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

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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

#ifndef gameexec_h_
#define gameexec_h_

#include "build.h"
#include "sector.h"  // mapstate_t
#include "gamedef.h"  // vmstate_t
#include "events_defs.h"

int32_t VM_ExecuteEvent(int const nEventID, int const spriteNum, int const playerNum, int const nDist, int32_t const nReturn);
int32_t VM_ExecuteEvent(int const nEventID, int const spriteNum, int const playerNum, int const nDist);
int32_t VM_ExecuteEvent(int const nEventID, int const spriteNum, int const playerNum);
int32_t VM_ExecuteEventWithValue(int const nEventID, int const spriteNum, int const playerNum, int32_t const nReturn);

static FORCE_INLINE int VM_HaveEvent(int const nEventID)
{
    return !!apScriptEvents[nEventID];
}

static FORCE_INLINE int32_t VM_OnEvent(int nEventID, int spriteNum, int playerNum, int nDist, int32_t nReturn)
{
    return VM_HaveEvent(nEventID) ? VM_ExecuteEvent(nEventID, spriteNum, playerNum, nDist, nReturn) : nReturn;
}

static FORCE_INLINE int32_t VM_OnEvent(int nEventID, int spriteNum, int playerNum, int nDist)
{
    return VM_HaveEvent(nEventID) ? VM_ExecuteEvent(nEventID, spriteNum, playerNum, nDist) : 0;
}

static FORCE_INLINE int32_t VM_OnEvent(int nEventID, int spriteNum = -1, int playerNum = -1)
{
    return VM_HaveEvent(nEventID) ? VM_ExecuteEvent(nEventID, spriteNum, playerNum) : 0;
}

static FORCE_INLINE int32_t VM_OnEventWithReturn(int nEventID, int spriteNum, int playerNum, int32_t nReturn)
{
    return VM_HaveEvent(nEventID) ? VM_ExecuteEventWithValue(nEventID, spriteNum, playerNum, nReturn) : nReturn;
}

#ifdef __cplusplus
extern "C" {
#endif

extern int32_t ticrandomseed;

extern vmstate_t vm;
extern int32_t g_tw;
extern int32_t g_currentEvent;
extern int32_t g_errorLineNum;

extern uint32_t g_actorCalls[MAXTILES];
extern double g_actorTotalMs[MAXTILES], g_actorMinMs[MAXTILES], g_actorMaxMs[MAXTILES];

void A_Execute(int spriteNum, int playerNum, int playerDist);
void A_Fall(int spriteNum);
void A_FallBomb(int spriteNum);
int32_t A_GetFurthestAngle(int spriteNum, int angDiv);
void A_GetZLimits(int spriteNum);
int32_t __fastcall G_GetAngleDelta(int32_t currAngle, int32_t newAngle);
//void G_RestoreMapState();
//void G_SaveMapState();

#define CON_ERRPRINTF(Text, ...) do { \
    OSD_Printf("Line %d, %s: " Text, g_errorLineNum, VM_GetKeywordForID(g_tw), ## __VA_ARGS__); \
} while (0)

#define CON_CRITICALERRPRINTF(Text, ...) do { \
    OSD_Printf("Line %d, %s: " Text, g_errorLineNum, VM_GetKeywordForID(g_tw), ## __VA_ARGS__); \
    wm_msgbox(APPNAME, "Line %d, %s: " Text, g_errorLineNum, VM_GetKeywordForID(g_tw), ## __VA_ARGS__); \
} while (0)

void G_GetTimeDate(int32_t * pValues);
int G_StartTrack(int levelNum);
void VM_UpdateAnim(int spriteNum, int32_t *pData);

#ifdef __cplusplus
}
#endif

#endif

repo_name: orochi/nblood

license: mit


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

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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


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

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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

#include "duke3d.h"
#include "menus.h"
#include "savegame.h"

#include "vfs.h"

#define gamevars_c_

gamevar_t   aGameVars[MAXGAMEVARS];
int32_t     g_gameVarCount   = 0;

// pointers to weapon gamevar data
intptr_t *aplWeaponClip[MAX_WEAPONS];           // number of items in magazine
intptr_t *aplWeaponFireDelay[MAX_WEAPONS];      // delay to fire
intptr_t *aplWeaponFireSound[MAX_WEAPONS];      // Sound made when firing (each time for automatic)
intptr_t *aplWeaponFlags[MAX_WEAPONS];          // Flags for weapon
intptr_t *aplWeaponFlashColor[MAX_WEAPONS];     // Muzzle flash color
intptr_t *aplWeaponHoldDelay[MAX_WEAPONS];      // delay after release fire button to fire (0 for none)
intptr_t *aplWeaponInitialSound[MAX_WEAPONS];   // Sound made when weapon starts firing. zero for no sound
intptr_t *aplWeaponReload[MAX_WEAPONS];         // delay to reload (include fire)
intptr_t *aplWeaponShoots[MAX_WEAPONS];         // what the weapon shoots
intptr_t *aplWeaponShotsPerBurst[MAX_WEAPONS];  // number of shots per 'burst' (one ammo per 'burst')
intptr_t *aplWeaponSound2Sound[MAX_WEAPONS];    // Alternate sound sound ID
intptr_t *aplWeaponSound2Time[MAX_WEAPONS];     // Alternate sound time
intptr_t *aplWeaponSpawn[MAX_WEAPONS];          // the item to spawn
intptr_t *aplWeaponSpawnTime[MAX_WEAPONS];      // the frame at which to spawn an item
intptr_t *aplWeaponTotalTime[MAX_WEAPONS];      // The total time the weapon is cycling before next fire.
intptr_t *aplWeaponWorksLike[MAX_WEAPONS];      // What original the weapon works like

// Frees the memory for the *values* of game variables and arrays. Resets their
// counts to zero. Call this function as many times as needed.
//
// Returns: old g_gameVarCount | (g_gameArrayCount<<16).
int Gv_Free(void)
{
    for (auto &gameVar : aGameVars)
    {
        if (gameVar.flags & GAMEVAR_USER_MASK)
            ALIGNED_FREE_AND_NULL(gameVar.pValues);
        gameVar.flags |= GAMEVAR_RESET;
    }

    EDUKE32_STATIC_ASSERT(MAXGAMEVARS < 32768);
    int const varCount = g_gameVarCount;
    g_gameVarCount = 0;

    hash_init(&h_gamevars);

    return varCount;
}

// Calls Gv_Free() and in addition frees the labels of all game variables and
// arrays.
// Only call this function at exit
void Gv_Clear(void)
{
    Gv_Free();

    // Now, only do work that Gv_Free() hasn't done.
    for (auto & gameVar : aGameVars)
        DO_FREE_AND_NULL(gameVar.szLabel);
}

int Gv_ReadSave(buildvfs_kfd kFile)
{
    char tbuf[12];

    if (kread(kFile, tbuf, 12)!=12) goto corrupt;
    if (Bmemcmp(tbuf, "BEG: EDuke32", 12)) { OSD_Printf("BEG ERR\n"); return 2; }

    Gv_Free(); // nuke 'em from orbit, it's the only way to be sure...

    if (kdfread_LZ4(&g_gameVarCount,sizeof(g_gameVarCount),1,kFile) != 1) goto corrupt;
    for (bssize_t i=0; i<g_gameVarCount; i++)
    {
        char *const olabel = aGameVars[i].szLabel;

        if (kdfread_LZ4(&aGameVars[i], sizeof(gamevar_t), 1, kFile) != 1)
            goto corrupt;

        aGameVars[i].szLabel = (char *)Xrealloc(olabel, MAXVARLABEL * sizeof(uint8_t));

        if (kdfread_LZ4(aGameVars[i].szLabel, MAXVARLABEL, 1, kFile) != 1)
            goto corrupt;
        hash_add(&h_gamevars, aGameVars[i].szLabel,i, 1);

        if (aGameVars[i].flags & GAMEVAR_PERPLAYER)
        {
            aGameVars[i].pValues = (intptr_t*)Xaligned_alloc(PLAYER_VAR_ALIGNMENT, MAXPLAYERS * sizeof(intptr_t));
            if (kdfread_LZ4(aGameVars[i].pValues,sizeof(intptr_t) * MAXPLAYERS, 1, kFile) != 1) goto corrupt;
        }
        else if (aGameVars[i].flags & GAMEVAR_PERACTOR)
        {
            aGameVars[i].pValues = (intptr_t*)Xaligned_alloc(ACTOR_VAR_ALIGNMENT, MAXSPRITES * sizeof(intptr_t));
            if (kdfread_LZ4(aGameVars[i].pValues,sizeof(intptr_t) * MAXSPRITES, 1, kFile) != 1) goto corrupt;
        }
    }

    Gv_InitWeaponPointers();

    // Gv_RefreshPointers();

    uint8_t savedstate[MAXVOLUMES*MAXLEVELS];
    Bmemset(savedstate, 0, sizeof(savedstate));

    if (kdfread_LZ4(savedstate, sizeof(savedstate), 1, kFile) != 1) goto corrupt;

    for (bssize_t i = 0; i < (MAXVOLUMES * MAXLEVELS); i++)
    {
        G_FreeMapState(i);

        if (!savedstate[i])
            continue;

        g_mapInfo[i].savedstate = (mapstate_t *)Xaligned_alloc(ACTOR_VAR_ALIGNMENT, sizeof(mapstate_t));
        if (kdfread_LZ4(g_mapInfo[i].savedstate, sizeof(mapstate_t), 1, kFile) != 1) return -8;

        mapstate_t &sv = *g_mapInfo[i].savedstate;

        for (bssize_t j = 0; j < g_gameVarCount; j++)
        {
            if (aGameVars[j].flags & GAMEVAR_NORESET) continue;
            if (aGameVars[j].flags & GAMEVAR_PERPLAYER)
            {
                sv.vars[j] = (intptr_t *) Xaligned_alloc(PLAYER_VAR_ALIGNMENT, MAXPLAYERS * sizeof(intptr_t));
                if (kdfread_LZ4(sv.vars[j], sizeof(intptr_t) * MAXPLAYERS, 1, kFile) != 1) return -9;
            }
            else if (aGameVars[j].flags & GAMEVAR_PERACTOR)
            {
                sv.vars[j] = (intptr_t *) Xaligned_alloc(ACTOR_VAR_ALIGNMENT, MAXSPRITES * sizeof(intptr_t));
                if (kdfread_LZ4(sv.vars[j], sizeof(intptr_t) * MAXSPRITES, 1, kFile) != 1) return -10;
            }
        }
    }

    if (kread(kFile, tbuf, 12) != 12) return -13;
    if (Bmemcmp(tbuf, "EOF: EDuke32", 12)) { OSD_Printf("EOF ERR\n"); return 2; }

    return 0;

corrupt:
    return -7;
}

void Gv_WriteSave(buildvfs_FILE fil)
{
    //   AddLog("Saving Game Vars to File");
    buildvfs_fwrite("BEG: EDuke32", 12, 1, fil);

    dfwrite_LZ4(&g_gameVarCount,sizeof(g_gameVarCount),1,fil);

    for (bssize_t i = 0; i < g_gameVarCount; i++)
    {
        dfwrite_LZ4(&(aGameVars[i]), sizeof(gamevar_t), 1, fil);
        dfwrite_LZ4(aGameVars[i].szLabel, sizeof(uint8_t) * MAXVARLABEL, 1, fil);

        if (aGameVars[i].flags & GAMEVAR_PERPLAYER)
            dfwrite_LZ4(aGameVars[i].pValues, sizeof(intptr_t) * MAXPLAYERS, 1, fil);
        else if (aGameVars[i].flags & GAMEVAR_PERACTOR)
            dfwrite_LZ4(aGameVars[i].pValues, sizeof(intptr_t) * MAXSPRITES, 1, fil);
    }

    uint8_t savedstate[MAXVOLUMES * MAXLEVELS];
    Bmemset(savedstate, 0, sizeof(savedstate));

    for (bssize_t i = 0; i < (MAXVOLUMES * MAXLEVELS); i++)
        if (g_mapInfo[i].savedstate != NULL)
            savedstate[i] = 1;

    dfwrite_LZ4(savedstate, sizeof(savedstate), 1, fil);

    for (bssize_t i = 0; i < (MAXVOLUMES * MAXLEVELS); i++)
    {
        if (!savedstate[i]) continue;

        mapstate_t &sv = *g_mapInfo[i].savedstate;

        dfwrite_LZ4(g_mapInfo[i].savedstate, sizeof(mapstate_t), 1, fil);

        for (bssize_t j = 0; j < g_gameVarCount; j++)
        {
            if (aGameVars[j].flags & GAMEVAR_NORESET) continue;
            if (aGameVars[j].flags & GAMEVAR_PERPLAYER)
                dfwrite_LZ4(sv.vars[j], sizeof(intptr_t) * MAXPLAYERS, 1, fil);
            else if (aGameVars[j].flags & GAMEVAR_PERACTOR)
                dfwrite_LZ4(sv.vars[j], sizeof(intptr_t) * MAXSPRITES, 1, fil);
        }
    }

    buildvfs_fwrite("EOF: EDuke32", 12, 1, fil);
}

void Gv_DumpValues(void)
{
    buildprint("// Current Game Definitions\n\n");

    for (bssize_t i=0; i<g_gameVarCount; i++)
    {
        buildprint("gamevar ", aGameVars[i].szLabel, " ");

        if (aGameVars[i].flags & (GAMEVAR_INT32PTR))
            buildprint(*(int32_t *)aGameVars[i].global);
        else if (aGameVars[i].flags & (GAMEVAR_INT16PTR))
            buildprint(*(int16_t *)aGameVars[i].global);
        else
            buildprint(aGameVars[i].global);

        if (aGameVars[i].flags & (GAMEVAR_PERPLAYER))
            buildprint(" GAMEVAR_PERPLAYER");
        else if (aGameVars[i].flags & (GAMEVAR_PERACTOR))
            buildprint(" GAMEVAR_PERACTOR");
        else
            buildprint(" ", aGameVars[i].flags/* & (GAMEVAR_USER_MASK)*/);

        buildprint(" // ");
        if (aGameVars[i].flags & (GAMEVAR_SYSTEM))
            buildprint(" (system)");
        if (aGameVars[i].flags & (GAMEVAR_PTR_MASK))
            buildprint(" (pointer)");
        if (aGameVars[i].flags & (GAMEVAR_READONLY))
            buildprint(" (read only)");
        if (aGameVars[i].flags & (GAMEVAR_SPECIAL))
            buildprint(" (special)");
        buildprint("\n");
    }
    buildprint("\n// end of game definitions\n");
}

// XXX: This function is very strange.
void Gv_ResetVars(void) /* this is called during a new game and nowhere else */
{
    Gv_Free();

    for (auto &aGameVar : aGameVars)
    {
        if (aGameVar.szLabel != NULL)
            Gv_NewVar(aGameVar.szLabel, (aGameVar.flags & GAMEVAR_NODEFAULT) ? aGameVar.global : aGameVar.defaultValue, aGameVar.flags);
    }
}

void Gv_NewVar(const char *pszLabel, intptr_t lValue, uint32_t dwFlags)
{
    if (EDUKE32_PREDICT_FALSE(g_gameVarCount >= MAXGAMEVARS))
    {
        g_errorCnt++;
        C_ReportError(-1);
        initprintf("%s:%d: error: too many gamevars!\n",g_scriptFileName,g_lineNumber);
        return;
    }

    if (EDUKE32_PREDICT_FALSE(Bstrlen(pszLabel) > (MAXVARLABEL-1)))
    {
        g_errorCnt++;
        C_ReportError(-1);
        initprintf("%s:%d: error: variable name `%s' exceeds limit of %d characters.\n",g_scriptFileName,g_lineNumber,pszLabel, MAXVARLABEL);
        return;
    }

    int gV = hash_find(&h_gamevars,pszLabel);

    if (gV >= 0 && !(aGameVars[gV].flags & GAMEVAR_RESET))
    {
        // found it...
        if (EDUKE32_PREDICT_FALSE(aGameVars[gV].flags & (GAMEVAR_PTR_MASK)))
        {
            C_ReportError(-1);
            initprintf("%s:%d: warning: cannot redefine internal gamevar `%s'.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6));
            return;
        }
        else if (EDUKE32_PREDICT_FALSE(!(aGameVars[gV].flags & GAMEVAR_SYSTEM)))
        {
            // it's a duplicate in error
            g_warningCnt++;
            C_ReportError(WARNING_DUPLICATEDEFINITION);
            return;
        }
    }

    if (gV == -1)
        gV = g_gameVarCount;

    // If it's a user gamevar...
    if ((aGameVars[gV].flags & GAMEVAR_SYSTEM) == 0)
    {
        // Allocate and set its label
        if (aGameVars[gV].szLabel == NULL)
            aGameVars[gV].szLabel = (char *)Xcalloc(MAXVARLABEL,sizeof(uint8_t));

        if (aGameVars[gV].szLabel != pszLabel)
            Bstrcpy(aGameVars[gV].szLabel,pszLabel);

        // and the flags
        aGameVars[gV].flags=dwFlags;

        // only free if per-{actor,player}
        if (aGameVars[gV].flags & GAMEVAR_USER_MASK)
            ALIGNED_FREE_AND_NULL(aGameVars[gV].pValues);
    }

    // if existing is system, they only get to change default value....
    aGameVars[gV].defaultValue = lValue;
    aGameVars[gV].flags &= ~GAMEVAR_RESET;

    if (gV == g_gameVarCount)
    {
        // we're adding a new one.
        hash_add(&h_gamevars, aGameVars[gV].szLabel, g_gameVarCount++, 0);
    }

    // Set initial values. (Or, override values for system gamevars.)
    if (aGameVars[gV].flags & GAMEVAR_PERPLAYER)
    {
        if (!aGameVars[gV].pValues)
        {
            aGameVars[gV].pValues = (intptr_t *) Xaligned_alloc(PLAYER_VAR_ALIGNMENT, MAXPLAYERS * sizeof(intptr_t));
            Bmemset(aGameVars[gV].pValues, 0, MAXPLAYERS * sizeof(intptr_t));
        }
        for (bssize_t j=MAXPLAYERS-1; j>=0; --j)
            aGameVars[gV].pValues[j]=lValue;
    }
    else if (aGameVars[gV].flags & GAMEVAR_PERACTOR)
    {
        if (!aGameVars[gV].pValues)
        {
            aGameVars[gV].pValues = (intptr_t *) Xaligned_alloc(ACTOR_VAR_ALIGNMENT, MAXSPRITES * sizeof(intptr_t));
            Bmemset(aGameVars[gV].pValues, 0, MAXSPRITES * sizeof(intptr_t));
        }
        for (bssize_t j=MAXSPRITES-1; j>=0; --j)
            aGameVars[gV].pValues[j]=lValue;
    }
    else aGameVars[gV].global = lValue;
}

static int Gv_GetVarIndex(const char *szGameLabel)
{
    int const gameVar = hash_find(&h_gamevars,szGameLabel);

    if (EDUKE32_PREDICT_FALSE((unsigned)gameVar >= MAXGAMEVARS))
    {
        OSD_Printf(OSD_ERROR "Gv_GetVarIndex(): INTERNAL ERROR: couldn't find gamevar %s!\n", szGameLabel);
        return 0;
    }

    return gameVar;
}

static FORCE_INLINE int __fastcall getvar__(int const gameVar, int const spriteNum, int const playerNum)
{
    auto const &var = aGameVars[gameVar & (MAXGAMEVARS-1)];

    int       returnValue  = 0;
    int const varFlags     = var.flags & (GAMEVAR_USER_MASK|GAMEVAR_PTR_MASK);

    if (!varFlags) returnValue = var.global;
    else if (varFlags == GAMEVAR_PERACTOR)
        returnValue = var.pValues[spriteNum & (MAXSPRITES-1)];
    else if (varFlags == GAMEVAR_PERPLAYER)
        returnValue = var.pValues[playerNum & (MAXPLAYERS-1)];
    else switch (varFlags & GAMEVAR_PTR_MASK)
    {
        case GAMEVAR_RAWQ16PTR:
        case GAMEVAR_INT32PTR: returnValue = *(int32_t *)var.global; break;
        case GAMEVAR_INT16PTR: returnValue = *(int16_t *)var.global; break;
        case GAMEVAR_Q16PTR:   returnValue = fix16_to_int(*(fix16_t *)var.global); break;
    }

    return NEGATE_ON_CONDITION(returnValue, gameVar & GV_FLAG_NEGATIVE);
}

int __fastcall Gv_GetVar(int const gameVar, int const spriteNum, int const playerNum) { return getvar__(gameVar, spriteNum, playerNum); }
int __fastcall Gv_GetVar(int const gameVar) { return getvar__(gameVar, vm.spriteNum, vm.playerNum); }

void __fastcall Gv_GetManyVars(int const numVars, int32_t * const outBuf)
{
    for (native_t j = 0; j < numVars; ++j)
        outBuf[j] = getvar__(*insptr++, vm.spriteNum, vm.playerNum);
}

static FORCE_INLINE void __fastcall setvar__(int const gameVar, int const newValue, int const spriteNum, int const playerNum)
{
    gamevar_t &var = aGameVars[gameVar];
    int const varFlags = var.flags & (GAMEVAR_USER_MASK|GAMEVAR_PTR_MASK);

    if (!varFlags) var.global=newValue;
    else if (varFlags == GAMEVAR_PERACTOR)
        var.pValues[spriteNum & (MAXSPRITES-1)] = newValue;
    else if (varFlags == GAMEVAR_PERPLAYER)
        var.pValues[playerNum & (MAXPLAYERS-1)] = newValue;
    else switch (varFlags & GAMEVAR_PTR_MASK)
    {
        case GAMEVAR_RAWQ16PTR:
        case GAMEVAR_INT32PTR: *((int32_t *)var.global) = (int32_t)newValue; break;
        case GAMEVAR_INT16PTR: *((int16_t *)var.global) = (int16_t)newValue; break;
        case GAMEVAR_Q16PTR:    *(fix16_t *)var.global  = fix16_from_int((int16_t)newValue); break;
    }
    return;
}

void __fastcall Gv_SetVar(int const gameVar, int const newValue) { setvar__(gameVar, newValue, vm.spriteNum, vm.playerNum); }
void __fastcall Gv_SetVar(int const gameVar, int const newValue, int const spriteNum, int const playerNum)
{
    setvar__(gameVar, newValue, spriteNum, playerNum);
}

int Gv_GetVarByLabel(const char *szGameLabel, int const defaultValue, int const spriteNum, int const playerNum)
{
    int const gameVar = hash_find(&h_gamevars, szGameLabel);
    return EDUKE32_PREDICT_TRUE(gameVar >= 0) ? Gv_GetVar(gameVar, spriteNum, playerNum) : defaultValue;
}

static intptr_t *Gv_GetVarDataPtr(const char *szGameLabel)
{
    int const gameVar = hash_find(&h_gamevars, szGameLabel);

    if (EDUKE32_PREDICT_FALSE((unsigned)gameVar >= MAXGAMEVARS))
        return NULL;

    gamevar_t &var = aGameVars[gameVar];

    if (var.flags & (GAMEVAR_USER_MASK | GAMEVAR_PTR_MASK))
        return var.pValues;

    return &(var.global);
}

void Gv_ResetSystemDefaults(void)
{
    // call many times...
    char aszBuf[64];

    //AddLog("ResetWeaponDefaults");

    for (int weaponNum = 0; weaponNum < MAX_WEAPONS; ++weaponNum)
    {
        for (int playerNum = 0; playerNum < MAXPLAYERS; ++playerNum)
        {
            Bsprintf(aszBuf, "WEAPON%d_CLIP", weaponNum);
            aplWeaponClip[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_RELOAD", weaponNum);
            aplWeaponReload[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_FIREDELAY", weaponNum);
            aplWeaponFireDelay[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_TOTALTIME", weaponNum);
            aplWeaponTotalTime[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_HOLDDELAY", weaponNum);
            aplWeaponHoldDelay[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_FLAGS", weaponNum);
            aplWeaponFlags[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_SHOOTS", weaponNum);
            aplWeaponShoots[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            if ((unsigned)aplWeaponShoots[weaponNum][playerNum] >= MAXTILES)
                aplWeaponShoots[weaponNum][playerNum] = 0;
            Bsprintf(aszBuf, "WEAPON%d_SPAWNTIME", weaponNum);
            aplWeaponSpawnTime[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_SPAWN", weaponNum);
            aplWeaponSpawn[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_SHOTSPERBURST", weaponNum);
            aplWeaponShotsPerBurst[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_WORKSLIKE", weaponNum);
            aplWeaponWorksLike[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_INITIALSOUND", weaponNum);
            aplWeaponInitialSound[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_FIRESOUND", weaponNum);
            aplWeaponFireSound[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_SOUND2TIME", weaponNum);
            aplWeaponSound2Time[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_SOUND2SOUND", weaponNum);
            aplWeaponSound2Sound[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
            Bsprintf(aszBuf, "WEAPON%d_FLASHCOLOR", weaponNum);
            aplWeaponFlashColor[weaponNum][playerNum] = Gv_GetVarByLabel(aszBuf, 0, -1, playerNum);
        }
    }

    g_aimAngleVarID  = Gv_GetVarIndex("AUTOAIMANGLE");
    g_angRangeVarID  = Gv_GetVarIndex("ANGRANGE");
    g_returnVarID    = Gv_GetVarIndex("RETURN");
    g_weaponVarID    = Gv_GetVarIndex("WEAPON");
    g_worksLikeVarID = Gv_GetVarIndex("WORKSLIKE");
    g_zRangeVarID    = Gv_GetVarIndex("ZRANGE");

    //AddLog("EOF:ResetWeaponDefaults");
}

// Will set members that were overridden at CON translation time to 1.
// For example, if
//   gamevar WEAPON1_SHOOTS 2200 GAMEVAR_PERPLAYER
// was specified at file scope, g_weaponOverridden[1].Shoots will be 1.
weapondata_t g_weaponOverridden[MAX_WEAPONS];

static weapondata_t weapondefaults[MAX_WEAPONS] = {
    /*
        WorksLike, Clip, Reload, FireDelay, TotalTime, HoldDelay,
        Flags,
        Shoots, SpawnTime, Spawn, ShotsPerBurst, InitialSound, FireSound, Sound2Time, Sound2Sound,
        FlashColor
    */
    {
        KNEE_WEAPON__STATIC, 0, 30, 7, 14, 14,
        WEAPON_RANDOMRESTART | WEAPON_AUTOMATIC,
        KNEE__STATIC, 0, 0, 0, 0, 0, 0,
        0
    },

    {
        PISTOL_WEAPON, 20, 50, 2, 5, 0,
        WEAPON_AUTOMATIC | WEAPON_HOLSTER_CLEARS_CLIP,
        SHOTSPARK1__STATIC, 2, SHELL__STATIC, 0, 0, PISTOL_FIRE__STATIC, 0, 0,
        255+(95<<8)
    },

    {
        SHOTGUN_WEAPON__STATIC, 0, 13, 4, 31, 0,
        WEAPON_CHECKATRELOAD,
        SHOTGUN__STATIC, 24, SHOTGUNSHELL__STATIC, 7, 0, SHOTGUN_FIRE__STATIC, 15, SHOTGUN_COCK__STATIC,
        255+(95<<8)
    },

    {
        CHAINGUN_WEAPON__STATIC, 0, 30, 1, 12, 10,
        WEAPON_AUTOMATIC | WEAPON_FIREEVERYTHIRD | WEAPON_AMMOPERSHOT,
        CHAINGUN__STATIC, 0, SHELL__STATIC, 0, 0, CHAINGUN_FIRE__STATIC, 0, 0,
        255+(95<<8)
    },

    {
        RPG_WEAPON__STATIC, 0, 30, 4, 20, 0,
        0,
        RPG__STATIC, 0, 0, 0, 0, 0, 0, 0,
        255+(95<<8)
    },

    {
        HANDBOMB_WEAPON__STATIC, 0, 30, 6, 19, 12,
        WEAPON_THROWIT,
        HEAVYHBOMB__STATIC, 0, 0, 0, 0, 0, 0,
        0
    },

    {
        SHRINKER_WEAPON__STATIC, 0, 0, 10, 30, 0,
        WEAPON_GLOWS,
        SHRINKER__STATIC, 0, 0, 0, SHRINKER_FIRE__STATIC, 0, 0, 0,
        176+(252<<8)+(120<<16)
    },

    {
        DEVISTATOR_WEAPON__STATIC, 0, 30, 2, 5, 5,
        WEAPON_FIREEVERYOTHER,
        RPG__STATIC, 0, 0, 2, CAT_FIRE__STATIC, 0, 0, 0,
        255+(95<<8)
    },

    {
        TRIPBOMB_WEAPON__STATIC, 0, 30, 3, 16, 0,
        WEAPON_STANDSTILL,
        HANDHOLDINGLASER__STATIC, 0, 0, 0, 0, 0, 0,
        0
    },

    {
        FREEZE_WEAPON__STATIC, 0, 0, 3, 5, 0,
        WEAPON_FIREEVERYOTHER,
        FREEZEBLAST__STATIC, 0, 0, 0, CAT_FIRE__STATIC, CAT_FIRE__STATIC, 0, 0,
        72+(88<<8)+(140<<16)
    },

    {
        HANDREMOTE_WEAPON__STATIC, 0, 30, 2, 10, 0,
        WEAPON_BOMB_TRIGGER | WEAPON_NOVISIBLE,
        0, 0, 0, 0, 0, 0, 0,
        0
    },

    {
        GROW_WEAPON__STATIC, 0, 0, 3, 30, 0,
        WEAPON_GLOWS,
        GROWSPARK__STATIC, 0, 0, 0, EXPANDERSHOOT__STATIC, EXPANDERSHOOT__STATIC, 0, 0,
        216+(52<<8)+(20<<16)
    },
};

// KEEPINSYNC with what is contained above
// XXX: ugly
static int32_t G_StaticToDynamicTile(int32_t const tile)
{
    switch (tile)
    {
#ifndef EDUKE32_STANDALONE
    case CHAINGUN__STATIC: return CHAINGUN;
    case FREEZEBLAST__STATIC: return FREEZEBLAST;
    case GROWSPARK__STATIC: return GROWSPARK;
    case HANDHOLDINGLASER__STATIC: return HANDHOLDINGLASER;
    case HEAVYHBOMB__STATIC: return HEAVYHBOMB;
    case KNEE__STATIC: return KNEE;
    case RPG__STATIC: return RPG;
    case SHELL__STATIC: return SHELL;
    case SHOTGUNSHELL__STATIC: return SHOTGUNSHELL;
    case SHOTGUN__STATIC: return SHOTGUN;
    case SHOTSPARK1__STATIC: return SHOTSPARK1;
    case SHRINKER__STATIC: return SHRINKER;
#endif
    default: return tile;
    }
}

static int32_t G_StaticToDynamicSound(int32_t const sound)
{
    switch (sound)
    {
#ifndef EDUKE32_STANDALONE
    case CAT_FIRE__STATIC: return CAT_FIRE;
    case CHAINGUN_FIRE__STATIC: return CHAINGUN_FIRE;
    case EJECT_CLIP__STATIC: return EJECT_CLIP;
    case EXPANDERSHOOT__STATIC: return EXPANDERSHOOT;
    case INSERT_CLIP__STATIC: return INSERT_CLIP;
    case PISTOL_FIRE__STATIC: return PISTOL_FIRE;
    case SELECT_WEAPON__STATIC: return SELECT_WEAPON;
    case SHOTGUN_FIRE__STATIC: return SHOTGUN_FIRE;
    case SHOTGUN_COCK__STATIC: return SHOTGUN_COCK;
    case SHRINKER_FIRE__STATIC: return SHRINKER_FIRE;
#endif
    default: return sound;
    }
}

// Initialize WEAPONx_* gamevars. Since for Lunatic, they reside on the C side,
// they're set directly. In C-CON, a new CON variable is defined together with
// its initial value.
#ifdef LUNATIC
# define ADDWEAPONVAR(Weapidx, Membname) do { \
    int32_t j; \
    for (j=0; j<MAXPLAYERS; j++) \
        g_playerWeapon[j][Weapidx].Membname = weapondefaults[Weapidx].Membname; \
} while (0)
#else
# define ADDWEAPONVAR(Weapidx, Membname) do { \
    Bsprintf(aszBuf, "WEAPON%d_" #Membname, Weapidx); \
    Bstrupr(aszBuf); \
    Gv_NewVar(aszBuf, weapondefaults[Weapidx].Membname, GAMEVAR_PERPLAYER | GAMEVAR_SYSTEM); \
} while (0)
#endif

// After CON translation, get not-overridden members from weapondefaults[] back
// into the live arrays! (That is, g_playerWeapon[][] for Lunatic, WEAPONx_*
// gamevars on the CON side in C-CON.)
#ifdef LUNATIC
# define POSTADDWEAPONVAR(Weapidx, Membname) ADDWEAPONVAR(Weapidx, Membname)
#else
// NYI
# define POSTADDWEAPONVAR(Weapidx, Membname) do {} while (0)
#endif

// Finish a default weapon member after CON translation. If it was not
// overridden from CON itself (see example at g_weaponOverridden[]), we set
// both the weapondefaults[] entry (probably dead by now) and the live value.
#define FINISH_WEAPON_DEFAULT_X(What, i, Membname) do {  \
    if (!g_weaponOverridden[i].Membname) \
    { \
        weapondefaults[i].Membname = G_StaticToDynamic##What(weapondefaults[i].Membname); \
        POSTADDWEAPONVAR(i, Membname); \
    } \
} while (0)

#define FINISH_WEAPON_DEFAULT_TILE(i, Membname) FINISH_WEAPON_DEFAULT_X(Tile, i, Membname)
#define FINISH_WEAPON_DEFAULT_SOUND(i, Membname) FINISH_WEAPON_DEFAULT_X(Sound, i, Membname)

// Process the dynamic {tile,sound} mappings after CON has been translated.
// We cannot do this before, because the dynamic maps are not yet set up then.
void Gv_FinalizeWeaponDefaults(void)
{
    for (int i=0; i<MAX_WEAPONS; i++)
    {
        FINISH_WEAPON_DEFAULT_TILE(i, Shoots);
        FINISH_WEAPON_DEFAULT_TILE(i, Spawn);

        FINISH_WEAPON_DEFAULT_SOUND(i, InitialSound);
        FINISH_WEAPON_DEFAULT_SOUND(i, FireSound);
        FINISH_WEAPON_DEFAULT_SOUND(i, Sound2Sound);
    }
}
#undef FINISH_WEAPON_DEFAULT_SOUND
#undef FINISH_WEAPON_DEFAULT_TILE
#undef FINISH_WEAPON_DEFAULT_X
#undef POSTADDWEAPONVAR

#if !defined LUNATIC
static int32_t lastvisinc;
#endif

static void Gv_AddSystemVars(void)
{
    // only call ONCE

    char aszBuf[64];

    for (int i=0; i<MAX_WEAPONS; i++)
    {
        ADDWEAPONVAR(i, Clip);
        ADDWEAPONVAR(i, FireDelay);
        ADDWEAPONVAR(i, FireSound);
        ADDWEAPONVAR(i, Flags);
        ADDWEAPONVAR(i, FlashColor);
        ADDWEAPONVAR(i, HoldDelay);
        ADDWEAPONVAR(i, InitialSound);
        ADDWEAPONVAR(i, Reload);
        ADDWEAPONVAR(i, Shoots);
        ADDWEAPONVAR(i, ShotsPerBurst);
        ADDWEAPONVAR(i, Sound2Sound);
        ADDWEAPONVAR(i, Sound2Time);
        ADDWEAPONVAR(i, Spawn);
        ADDWEAPONVAR(i, SpawnTime);
        ADDWEAPONVAR(i, TotalTime);
        ADDWEAPONVAR(i, WorksLike);
    }

    Gv_NewVar("GRENADE_LIFETIME",      NAM_GRENADE_LIFETIME,                    GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER);
    Gv_NewVar("GRENADE_LIFETIME_VAR",  NAM_GRENADE_LIFETIME_VAR,                GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER);
    Gv_NewVar("STICKYBOMB_LIFETIME",   NAM_GRENADE_LIFETIME,                    GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER);
    Gv_NewVar("STICKYBOMB_LIFETIME_VAR", NAM_GRENADE_LIFETIME_VAR,              GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER);
    Gv_NewVar("TRIPBOMB_CONTROL",      TRIPBOMB_TRIPWIRE,                       GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER);

    Gv_NewVar("ANGRANGE",              18,                                      GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER);
    Gv_NewVar("AUTOAIMANGLE",          0,                                       GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER);
    Gv_NewVar("COOP",                  (intptr_t)&ud.coop,                      GAMEVAR_SYSTEM | GAMEVAR_INT32PTR);
    Gv_NewVar("FFIRE",                 (intptr_t)&ud.ffire,                     GAMEVAR_SYSTEM | GAMEVAR_INT32PTR);
    Gv_NewVar("LEVEL",                 (intptr_t)&ud.level_number,              GAMEVAR_SYSTEM | GAMEVAR_INT32PTR | GAMEVAR_READONLY);
    Gv_NewVar("MARKER",                (intptr_t)&ud.marker,                    GAMEVAR_SYSTEM | GAMEVAR_INT32PTR);
    Gv_NewVar("MONSTERS_OFF",          (intptr_t)&ud.monsters_off,              GAMEVAR_SYSTEM | GAMEVAR_INT32PTR);
    Gv_NewVar("MULTIMODE",             (intptr_t)&ud.multimode,                 GAMEVAR_SYSTEM | GAMEVAR_INT32PTR);
    Gv_NewVar("RESPAWN_INVENTORY",     (intptr_t)&ud.respawn_inventory,         GAMEVAR_SYSTEM | GAMEVAR_INT32PTR);
    Gv_NewVar("RESPAWN_ITEMS",         (intptr_t)&ud.respawn_items,             GAMEVAR_SYSTEM | GAMEVAR_INT32PTR);
    Gv_NewVar("RESPAWN_MONSTERS",      (intptr_t)&ud.respawn_monsters,          GAMEVAR_SYSTEM | GAMEVAR_INT32PTR);
    Gv_NewVar("RETURN",                0,                                       GAMEVAR_SYSTEM);
    Gv_NewVar("VOLUME",                (intptr_t)&ud.volume_number,             GAMEVAR_SYSTEM | GAMEVAR_INT32PTR | GAMEVAR_READONLY);
    Gv_NewVar("WEAPON",                0,                                       GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER | GAMEVAR_READONLY);
    Gv_NewVar("WORKSLIKE",             0,                                       GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER | GAMEVAR_READONLY);
    Gv_NewVar("ZRANGE",                4,                                       GAMEVAR_SYSTEM | GAMEVAR_PERPLAYER);
}

#undef ADDWEAPONVAR

void Gv_Init(void)
{
#if !defined LUNATIC
    // already initialized
    if (aGameVars[0].flags)
        return;
#else
    static int32_t inited=0;
    if (inited)
        return;
    inited = 1;
#endif

    // Set up weapon defaults, g_playerWeapon[][].
    Gv_AddSystemVars();
#if !defined LUNATIC
    Gv_InitWeaponPointers();
#endif
    Gv_ResetSystemDefaults();
}

#if !defined LUNATIC
void Gv_InitWeaponPointers(void)
{
    char aszBuf[64];
    // called from game Init AND when level is loaded...

    //AddLog("Gv_InitWeaponPointers");

    for (int i=(MAX_WEAPONS-1); i>=0; i--)
    {
        Bsprintf(aszBuf, "WEAPON%d_CLIP", i);
        aplWeaponClip[i] = Gv_GetVarDataPtr(aszBuf);

        if (!aplWeaponClip[i])
        {
            initprintf("ERROR: NULL weapon!  WTF?! %s\n", aszBuf);
            // Bexit(EXIT_SUCCESS);
            G_Shutdown();
        }

        Bsprintf(aszBuf, "WEAPON%d_RELOAD", i);
        aplWeaponReload[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_FIREDELAY", i);
        aplWeaponFireDelay[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_TOTALTIME", i);
        aplWeaponTotalTime[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_HOLDDELAY", i);
        aplWeaponHoldDelay[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_FLAGS", i);
        aplWeaponFlags[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_SHOOTS", i);
        aplWeaponShoots[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_SPAWNTIME", i);
        aplWeaponSpawnTime[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_SPAWN", i);
        aplWeaponSpawn[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_SHOTSPERBURST", i);
        aplWeaponShotsPerBurst[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_WORKSLIKE", i);
        aplWeaponWorksLike[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_INITIALSOUND", i);
        aplWeaponInitialSound[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_FIRESOUND", i);
        aplWeaponFireSound[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_SOUND2TIME", i);
        aplWeaponSound2Time[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_SOUND2SOUND", i);
        aplWeaponSound2Sound[i] = Gv_GetVarDataPtr(aszBuf);
        Bsprintf(aszBuf, "WEAPON%d_FLASHCOLOR", i);
        aplWeaponFlashColor[i] = Gv_GetVarDataPtr(aszBuf);
    }
}

void Gv_RefreshPointers(void)
{
    aGameVars[Gv_GetVarIndex("COOP")].global              = (intptr_t)&ud.coop;
    aGameVars[Gv_GetVarIndex("FFIRE")].global             = (intptr_t)&ud.ffire;
    aGameVars[Gv_GetVarIndex("LEVEL")].global             = (intptr_t)&ud.level_number;
    aGameVars[Gv_GetVarIndex("MARKER")].global            = (intptr_t)&ud.marker;
    aGameVars[Gv_GetVarIndex("MONSTERS_OFF")].global      = (intptr_t)&ud.monsters_off;
    aGameVars[Gv_GetVarIndex("MULTIMODE")].global         = (intptr_t)&ud.multimode;
    aGameVars[Gv_GetVarIndex("RESPAWN_INVENTORY")].global = (intptr_t)&ud.respawn_inventory;
    aGameVars[Gv_GetVarIndex("RESPAWN_ITEMS")].global     = (intptr_t)&ud.respawn_items;
    aGameVars[Gv_GetVarIndex("RESPAWN_MONSTERS")].global  = (intptr_t)&ud.respawn_monsters;
    aGameVars[Gv_GetVarIndex("VOLUME")].global            = (intptr_t)&ud.volume_number;
}
#endif


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

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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

#ifndef gamevars_h_
#define gamevars_h_

#define MAXGAMEVARS 2048 // must be a power of two
#define MAXVARLABEL 26

#define GV_FLAG_CONSTANT (MAXGAMEVARS)
#define GV_FLAG_NEGATIVE (MAXGAMEVARS<<1)

// store global game definitions
enum GamevarFlags_t
{
    GAMEVAR_PERPLAYER = 0x00000001,  // per-player variable
    GAMEVAR_PERACTOR  = 0x00000002,  // per-actor variable
    GAMEVAR_USER_MASK = (GAMEVAR_PERPLAYER | GAMEVAR_PERACTOR),
    GAMEVAR_RESET     = 0x00000008,  // INTERNAL, don't use
    GAMEVAR_DEFAULT   = 0x00000100,  // UNUSED, but always cleared for user-defined gamevars
    GAMEVAR_NODEFAULT = 0x00000400,  // don't reset on actor spawn
    GAMEVAR_SYSTEM    = 0x00000800,  // cannot change mode flags...(only default value)
    GAMEVAR_READONLY  = 0x00001000,  // values are read-only (no setvar allowed)
    GAMEVAR_INT32PTR  = 0x00002000,  // plValues is a pointer to an int32_t
    GAMEVAR_INT16PTR  = 0x00008000,  // plValues is a pointer to a short
    GAMEVAR_NORESET   = 0x00020000,  // var values are not reset when restoring map state
    GAMEVAR_SPECIAL   = 0x00040000,  // flag for structure member shortcut vars
    GAMEVAR_NOMULTI   = 0x00080000,  // don't attach to multiplayer packets
    GAMEVAR_Q16PTR    = 0x00100000,  // plValues is a pointer to a q16.16
    GAMEVAR_SERIALIZE = 0x00200000,  // write into permasaves

    GAMEVAR_RAWQ16PTR = GAMEVAR_Q16PTR | GAMEVAR_SPECIAL,  // plValues is a pointer to a q16.16 but we don't want conversion
    GAMEVAR_PTR_MASK  = GAMEVAR_INT32PTR | GAMEVAR_INT16PTR | GAMEVAR_Q16PTR | GAMEVAR_RAWQ16PTR,
};

// Alignments for per-player and per-actor variables.
#define PLAYER_VAR_ALIGNMENT (sizeof(intptr_t))
#define ACTOR_VAR_ALIGNMENT 16

#define ARRAY_ALIGNMENT 16

#pragma pack(push,1)
typedef struct
{
    union {
        intptr_t  global;
        intptr_t *pValues;  // array of values when 'per-player', or 'per-actor'
    };
    intptr_t  defaultValue;
    uintptr_t flags;
    char *    szLabel;
} gamevar_t;
#pragma pack(pop)

extern gamevar_t   aGameVars[MAXGAMEVARS];
extern int32_t     g_gameVarCount;

int __fastcall Gv_GetVar(int const gameVar, int const spriteNum, int const playerNum);
void __fastcall Gv_SetVar(int const gameVar, int const newValue, int const spriteNum, int const playerNum);
int __fastcall Gv_GetVar(int const gameVar);
void __fastcall Gv_GetManyVars(int const numVars, int32_t * const outBuf);
void __fastcall Gv_SetVar(int const gameVar, int const newValue);

template <typename T>
static FORCE_INLINE void Gv_FillWithVars(T & rv)
{
    EDUKE32_STATIC_ASSERT(sizeof(T) % sizeof(int32_t) == 0);
    EDUKE32_STATIC_ASSERT(sizeof(T) > sizeof(int32_t));
    Gv_GetManyVars(sizeof(T)/sizeof(int32_t), (int32_t *)&rv);
}

int Gv_GetVarByLabel(const char *szGameLabel,int defaultValue,int spriteNum,int playerNum);
void Gv_NewVar(const char *pszLabel,intptr_t lValue,uint32_t dwFlags);

static FORCE_INLINE void A_ResetVars(int const spriteNum)
{
    for (auto &gv : aGameVars)
    {
        if ((gv.flags & (GAMEVAR_PERACTOR|GAMEVAR_NODEFAULT)) == GAMEVAR_PERACTOR)
            gv.pValues[spriteNum] = gv.defaultValue;
    }
}
void Gv_DumpValues(void);
void Gv_InitWeaponPointers(void);
void Gv_RefreshPointers(void);
void Gv_ResetVars(void);
int Gv_ReadSave(buildvfs_kfd kFile);
void Gv_WriteSave(buildvfs_FILE fil);
void Gv_Clear(void);

void Gv_ResetSystemDefaults(void);
void Gv_Init(void);
void Gv_FinalizeWeaponDefaults(void);

#define VM_GAMEVAR_OPERATOR(func, operator)                                                            \
    static FORCE_INLINE ATTRIBUTE((flatten)) void __fastcall func(int const id, int32_t const operand) \
    {                                                                                \
        auto &var = aGameVars[id];                                                                     \
                                                                                \
        switch (var.flags & (GAMEVAR_USER_MASK | GAMEVAR_PTR_MASK))                                    \
        {                                                                                \
            default:                                                                                \
                var.global operator operand;                                                           \
                break;                                                                                \
            case GAMEVAR_PERPLAYER:                                                                    \
                var.pValues[vm.playerNum & (MAXPLAYERS-1)] operator operand;                           \
                break;                                                                                \
            case GAMEVAR_PERACTOR:                                                                     \
                var.pValues[vm.spriteNum & (MAXSPRITES-1)] operator operand;                           \
                break;                                                                                \
            case GAMEVAR_INT32PTR: *(int32_t *)var.pValues operator(int32_t) operand; break;           \
            case GAMEVAR_INT16PTR: *(int16_t *)var.pValues operator(int16_t) operand; break;           \
            case GAMEVAR_Q16PTR:                                                                       \
            {                                                                                \
                Fix16 *pfix = (Fix16 *)var.global;                                                     \
                *pfix  operator fix16_from_int(operand);                                               \
                break;                                                                                \
            }                                                                                \
        }                                                                                \
    }

VM_GAMEVAR_OPERATOR(Gv_AddVar, +=)
VM_GAMEVAR_OPERATOR(Gv_SubVar, -=)

#undef VM_GAMEVAR_OPERATOR

#endif


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

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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

#define global_c_
#include "global.h"
#include "duke3d.h"

user_defs ud;

const char *s_buildDate = "20120522";

#ifdef __cplusplus
extern "C" {
#endif

char    g_volumeNames[MAXVOLUMES][33] = { "L.A. Meltdown", "Lunar Apocalypse", "Shrapnel City" };
char    g_skillNames[MAXSKILLS][33] = { "Piece Of Cake", "Let's Rock", "Come Get Some", "Damn I'm Good" };
char    g_gametypeNames[MAXGAMETYPES][33]
= { "DukeMatch (Spawn)", "Cooperative Play", "DukeMatch (No Spawn)", "Team DM (Spawn)", "Team DM (No Spawn)" };

int32_t g_volumeFlags[MAXVOLUMES];

int32_t g_gametypeFlags[MAXGAMETYPES] =
{
    GAMETYPE_FRAGBAR |
    GAMETYPE_SCORESHEET |
    GAMETYPE_DMSWITCHES |
    GAMETYPE_ITEMRESPAWN |
    GAMETYPE_MARKEROPTION |
    GAMETYPE_ACCESSATSTART,

    GAMETYPE_COOP |
    GAMETYPE_WEAPSTAY |
    GAMETYPE_COOPSPAWN |
    GAMETYPE_ACCESSCARDSPRITES |
    GAMETYPE_COOPVIEW |
    GAMETYPE_COOPSOUND |
    GAMETYPE_OTHERPLAYERSINMAP |
    GAMETYPE_PLAYERSFRIENDLY |
    GAMETYPE_FIXEDRESPAWN |
    GAMETYPE_PRESERVEINVENTORYDEATH,

    GAMETYPE_WEAPSTAY |
    GAMETYPE_FRAGBAR |
    GAMETYPE_SCORESHEET |
    GAMETYPE_DMSWITCHES |
    GAMETYPE_ACCESSATSTART,

    GAMETYPE_FRAGBAR |
    GAMETYPE_SCORESHEET |
    GAMETYPE_DMSWITCHES |
    GAMETYPE_ITEMRESPAWN |
    GAMETYPE_MARKEROPTION |
    GAMETYPE_ACCESSATSTART |
    GAMETYPE_TDM |
    GAMETYPE_TDMSPAWN,

    GAMETYPE_WEAPSTAY |
    GAMETYPE_FRAGBAR |
    GAMETYPE_SCORESHEET |
    GAMETYPE_DMSWITCHES |
    GAMETYPE_ACCESSATSTART |
    GAMETYPE_TDM |
    GAMETYPE_TDMSPAWN,
};

float g_gameUpdateAvgTime = -1.f;

int32_t g_actorRespawnTime   = 768;
int32_t g_bouncemineRadius   = 2500;
int32_t g_deleteQueueSize    = 64;
int32_t g_itemRespawnTime    = 768;

int32_t g_morterRadius       = 2500;
int32_t g_numFreezeBounces   = 3;
int32_t g_gametypeCnt       = 5;
int32_t g_volumeCnt         = 3;
int32_t g_pipebombRadius     = 2500;
int32_t g_playerFriction     = 0xCFD0;
int32_t g_rpgRadius          = 1780;
int32_t g_scriptSize         = 1048576;
int32_t g_seenineRadius      = 2048;
int32_t g_shrinkerRadius     = 680;
int32_t g_spriteGravity      = 176;
int32_t g_timerTicsPerSecond = TICRATE;
int32_t g_tripbombRadius     = 3880;
int32_t g_impactDamage       = 10;

int16_t g_blimpSpawnItems[15] =
{
    RPGSPRITE__STATIC,
    CHAINGUNSPRITE__STATIC,
    DEVISTATORAMMO__STATIC,
    RPGAMMO__STATIC,
    RPGAMMO__STATIC,
    JETPACK__STATIC,
    SHIELD__STATIC,
    FIRSTAID__STATIC,
    STEROIDS__STATIC,
    RPGAMMO__STATIC,
    RPGAMMO__STATIC,
    RPGSPRITE__STATIC,
    RPGAMMO__STATIC,
    FREEZESPRITE__STATIC,
    FREEZEAMMO__STATIC
};

char CheatKeys[2]       = { sc_D, sc_N };

char g_setupFileName[BMAX_PATH] = SETUPFILENAME;

#ifdef __cplusplus
}
#endif


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

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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

#ifndef global_h_
#define global_h_

#include "build.h"
#include "compat.h"
#include "duke3d.h"
#include "mmulti.h"
#include "quotes.h"
#include "sector.h"
#include "sounds.h"

#define MAXMINECARTS 16
#define MAXJAILDOORS 32
#define MAXLIGHTNINSECTORS 64
#define MAXTORCHSECTORS 64
#define MAXGEOSECTORS 64

#ifdef global_c_
    #define G_EXTERN
#else
    #define G_EXTERN extern
#endif

#define MAXINTERPOLATIONS MAXSPRITES
// KEEPINSYNC lunatic/con_lang.lua
#define MAXSKILLS 7

// duke3d global soup :(

G_EXTERN int32_t g_interpolationCnt;
G_EXTERN int32_t g_interpolationLock;
G_EXTERN int32_t oldipos[MAXINTERPOLATIONS];
G_EXTERN int32_t *curipos[MAXINTERPOLATIONS];
G_EXTERN int32_t bakipos[MAXINTERPOLATIONS];

#ifdef __cplusplus
extern "C" {
#endif

G_EXTERN int32_t duke3d_globalflags;

// KEEPINSYNC astub.c (used values only)
enum DUKE3D_GLOBALFLAGS {
    DUKE3D_NO_WIDESCREEN_PINNING = 1<<0,
    DUKE3D_NO_HARDCODED_FOGPALS = 1<<1,
    DUKE3D_NO_PALETTE_CHANGES = 1<<2,
};

G_EXTERN DukeStatus_t sbar;
G_EXTERN actor_t actor[MAXSPRITES];
// g_tile: tile-specific data THAT DOES NOT CHANGE during the course of a game
G_EXTERN tiledata_t g_tile[MAXTILES];
G_EXTERN animwalltype animwall[MAXANIMWALLS];
G_EXTERN char *apStrings[MAXQUOTES],*apXStrings[MAXQUOTES];
G_EXTERN char *label;
G_EXTERN int32_t g_musicIndex;
G_EXTERN char g_loadFromGroupOnly;
G_EXTERN char g_skillCnt;
G_EXTERN char pus,pub;
G_EXTERN char ready2send;
#define MAXPLAYERNAME 32
G_EXTERN char szPlayerName[MAXPLAYERNAME];
G_EXTERN char tempbuf[MAXSECTORS<<1],buf[1024];
G_EXTERN uint8_t packbuf[PACKBUF_SIZE];
#define TYPEBUFSIZE 141
G_EXTERN char typebuf[TYPEBUFSIZE];

G_EXTERN input_t localInput;
G_EXTERN input_t recsync[RECSYNCBUFSIZ];

//G_EXTERN uint8_t syncstat, syncval[MAXPLAYERS][MOVEFIFOSIZ];
//G_EXTERN int32_t syncvalhead[MAXPLAYERS], syncvaltail, syncvaltottail;

G_EXTERN int32_t avgfvel, avgsvel, avgbits;
G_EXTERN fix16_t avgavel, avghorz;
G_EXTERN int8_t avgextbits;

G_EXTERN int32_t movefifosendplc;
G_EXTERN int32_t movefifoplc;

G_EXTERN int32_t predictfifoplc;
G_EXTERN vec3_t mypos, omypos, myvel;
G_EXTERN fix16_t myhoriz, omyhoriz, myhorizoff, omyhorizoff, myang, omyang;
G_EXTERN int16_t mycursectnum, myjumpingcounter;
G_EXTERN uint8_t myjumpingtoggle, myonground, myhardlanding, myreturntocenter;
G_EXTERN int16_t my_moto_speed;
G_EXTERN uint8_t my_not_on_water, my_moto_on_ground;
G_EXTERN uint8_t my_moto_do_bump, my_moto_bump_fast, my_moto_on_oil, my_moto_on_mud;
G_EXTERN int16_t my_moto_bump, my_moto_bump_target, my_moto_turb;
G_EXTERN int32_t my_stairs;

G_EXTERN vec3_t myposbak[MOVEFIFOSIZ];
G_EXTERN fix16_t myhorizbak[MOVEFIFOSIZ], myangbak[MOVEFIFOSIZ];
G_EXTERN int32_t myminlag[MAXPLAYERS], mymaxlag, otherminlag, bufferjitter;

G_EXTERN int32_t g_networkBroadcastMode, g_movesPerPacket;

G_EXTERN int32_t g_animWallCnt;
G_EXTERN int32_t g_animateCnt;
G_EXTERN int32_t g_cloudCnt;
G_EXTERN int32_t g_curViewscreen;
G_EXTERN int32_t g_frameRate;
G_EXTERN int32_t g_cyclerCnt;
G_EXTERN int32_t g_damageCameras;
G_EXTERN int32_t g_defaultLabelCnt;
G_EXTERN int32_t g_doQuickSave;
G_EXTERN int32_t g_earthquakeTime;
G_EXTERN int32_t g_freezerSelfDamage;
G_EXTERN int32_t g_gameQuit;
G_EXTERN int32_t g_globalRandom;
G_EXTERN int32_t g_labelCnt;
G_EXTERN int32_t g_maxPlayerHealth;
G_EXTERN int32_t g_mirrorCount;
G_EXTERN int32_t g_mostConcurrentPlayers;
G_EXTERN int32_t g_musicSize;
G_EXTERN int32_t g_playerSpawnCnt;
G_EXTERN int32_t g_scriptDebug;
G_EXTERN int32_t g_showShareware;
G_EXTERN int32_t g_spriteDeleteQueuePos;
G_EXTERN int32_t g_startArmorAmount;
G_EXTERN int32_t g_tripbombLaserMode;
G_EXTERN int32_t screenpeek;

G_EXTERN int16_t g_animateSect[MAXANIMATES];
G_EXTERN int32_t *g_animatePtr[MAXANIMATES];
G_EXTERN int32_t g_animateGoal[MAXANIMATES];
G_EXTERN int32_t g_animateVel[MAXANIMATES];

G_EXTERN int16_t g_cloudSect[256];
G_EXTERN int16_t g_cloudX;
G_EXTERN int16_t g_cloudY;
G_EXTERN ClockTicks g_cloudClock;

G_EXTERN int16_t SpriteDeletionQueue[1024];
G_EXTERN int16_t g_cyclers[MAXCYCLERS][6];
G_EXTERN int16_t g_mirrorSector[64];
G_EXTERN int16_t g_mirrorWall[64];
G_EXTERN int32_t *labelcode;
G_EXTERN int32_t *labeltype;
G_EXTERN ClockTicks lockclock;
G_EXTERN ClockTicks ototalclock;

G_EXTERN int32_t g_wupass;
G_EXTERN int32_t g_chickenPlant;
G_EXTERN int32_t g_thunderOn;
G_EXTERN int32_t g_ufoSpawn;
G_EXTERN int32_t g_ufoCnt;
G_EXTERN int32_t g_hulkSpawn;
G_EXTERN int32_t g_vixenLevel;
G_EXTERN int32_t g_lastLevel;
G_EXTERN int32_t g_turdLevel;

G_EXTERN int32_t g_mineCartDir[MAXMINECARTS];
G_EXTERN int32_t g_mineCartSpeed[MAXMINECARTS];
G_EXTERN int32_t g_mineCartChildSect[MAXMINECARTS];
G_EXTERN int32_t g_mineCartSound[MAXMINECARTS];
G_EXTERN int32_t g_mineCartDist[MAXMINECARTS];
G_EXTERN int32_t g_mineCartDrag[MAXMINECARTS];
G_EXTERN int32_t g_mineCartOpen[MAXMINECARTS];
G_EXTERN int32_t g_mineCartSect[MAXMINECARTS];
G_EXTERN uint32_t g_mineCartCnt;

G_EXTERN int32_t g_jailDoorSound[MAXJAILDOORS];
G_EXTERN int32_t g_jailDoorDrag[MAXJAILDOORS];
G_EXTERN int32_t g_jailDoorSpeed[MAXJAILDOORS];
G_EXTERN int32_t g_jailDoorSecHitag[MAXJAILDOORS];
G_EXTERN int32_t g_jailDoorDist[MAXJAILDOORS];
G_EXTERN int32_t g_jailDoorDir[MAXJAILDOORS];
G_EXTERN int32_t g_jailDoorOpen[MAXJAILDOORS];
G_EXTERN int32_t g_jailDoorSect[MAXJAILDOORS];
G_EXTERN uint32_t g_jailDoorCnt;

G_EXTERN int32_t g_lightninSector[MAXLIGHTNINSECTORS];
G_EXTERN int32_t g_lightninSectorShade[MAXLIGHTNINSECTORS];
G_EXTERN uint32_t g_lightninCnt;

G_EXTERN int32_t g_torchSector[MAXTORCHSECTORS];
G_EXTERN int32_t g_torchSectorShade[MAXTORCHSECTORS];
G_EXTERN int32_t g_torchType[MAXTORCHSECTORS];
G_EXTERN uint32_t g_torchCnt;

G_EXTERN int32_t g_geoSectorWarp[MAXGEOSECTORS];
G_EXTERN int32_t g_geoSectorWarp2[MAXGEOSECTORS];
G_EXTERN int32_t g_geoSector[MAXGEOSECTORS];
G_EXTERN int32_t g_geoSectorX[MAXGEOSECTORS];
G_EXTERN int32_t g_geoSectorY[MAXGEOSECTORS];
G_EXTERN int32_t g_geoSectorX2[MAXGEOSECTORS];
G_EXTERN int32_t g_geoSectorY2[MAXGEOSECTORS];
G_EXTERN uint32_t g_geoSectorCnt;

G_EXTERN int32_t g_thunderFlash;
G_EXTERN int32_t g_thunderTime;
G_EXTERN int32_t g_winderFlash;
G_EXTERN int32_t g_winderTime;
G_EXTERN int32_t g_brightness;

G_EXTERN int16_t g_ambientLotag[64];
G_EXTERN int16_t g_ambientHitag[64];
G_EXTERN uint32_t g_ambientCnt;

G_EXTERN intptr_t *apScript;
G_EXTERN intptr_t *g_scriptPtr;

G_EXTERN map_t g_mapInfo[(MAXVOLUMES + 1) * MAXLEVELS];  // +1 volume for "intro", "briefing" and "loading" music
G_EXTERN vec2_t g_origins[MAXANIMPOINTS];

G_EXTERN int32_t g_windTime, g_windDir;
G_EXTERN int16_t g_fakeBubbaCnt, g_mamaSpawnCnt, g_banjoSong, g_bellTime, g_bellSprite;
G_EXTERN uint8_t g_spriteExtra[MAXSPRITES], g_sectorExtra[MAXSECTORS];
G_EXTERN uint8_t g_changeEnemySize, g_slotWin, g_ufoSpawnMinion, g_pistonSound, g_chickenWeaponTimer, g_RAendLevel, g_RAendEpisode, g_fogType;
G_EXTERN int32_t g_cdTrack;

// XXX: I think this pragma pack is meaningless here.
// MSDN (https://msdn.microsoft.com/en-us/library/2e70t5y1%28VS.80%29.aspx) says:
// "pack takes effect at the first struct, union, or class declaration after
//  the pragma is seen; pack has no effect on definitions."
#pragma pack(push,1)
#ifdef global_c_
static playerdata_t g_player_s[1 + MAXPLAYERS];
playerdata_t *const g_player = &g_player_s[1];
#else
extern playerdata_t *const g_player;
#endif
G_EXTERN playerspawn_t g_playerSpawnPoints[MAXPLAYERS];
G_EXTERN input_t inputfifo[MOVEFIFOSIZ][MAXPLAYERS];
#pragma pack(pop)

G_EXTERN char g_soundlocks[MAXSOUNDS];
G_EXTERN int32_t g_noEnemies;
G_EXTERN int32_t g_restorePalette;
G_EXTERN int32_t g_screenCapture;
G_EXTERN sound_t g_sounds[MAXSOUNDS];
G_EXTERN uint32_t everyothertime;
G_EXTERN uint32_t g_moveThingsCount;
G_EXTERN double g_gameUpdateTime;
G_EXTERN double g_gameUpdateAndDrawTime;
#define GAMEUPDATEAVGTIMENUMSAMPLES 100
extern float g_gameUpdateAvgTime;

#ifndef global_c_
extern char CheatKeys[2];
extern char g_gametypeNames[MAXGAMETYPES][33];
extern char g_setupFileName[BMAX_PATH];
extern char g_skillNames[MAXSKILLS][33];
extern char g_volumeNames[MAXVOLUMES][33];

extern int32_t g_actorRespawnTime;
extern int32_t g_bouncemineRadius;
extern int32_t g_deleteQueueSize;
extern int32_t g_gametypeCnt;
extern int32_t g_itemRespawnTime;
extern int32_t g_morterRadius;
extern int32_t g_numFreezeBounces;
extern int32_t g_pipebombRadius;
extern int32_t g_playerFriction;
extern int32_t g_rpgRadius;
extern int32_t g_scriptSize;
extern int32_t g_seenineRadius;
extern int32_t g_shrinkerRadius;
extern int32_t g_spriteGravity;
extern int32_t g_timerTicsPerSecond;
extern int32_t g_tripbombRadius;
extern int32_t g_impactDamage;
extern int32_t g_volumeCnt;

extern int16_t g_blimpSpawnItems[15];
extern int32_t g_gametypeFlags[MAXGAMETYPES];
extern int32_t g_volumeFlags[MAXVOLUMES];

extern const char *s_buildDate;
#endif

enum
{
    EF_HIDEFROMSP = 1<<0,
    // EF_HIDEFROMMP = 1<<1,
};

EXTERN_INLINE_HEADER void G_UpdateInterpolations(void);
EXTERN_INLINE_HEADER void G_RestoreInterpolations(void);

#ifdef __cplusplus
}
#endif

#if defined global_c_ || !defined DISABLE_INLINING

EXTERN_INLINE void G_UpdateInterpolations(void)  //Stick at beginning of G_DoMoveThings
{
    for (bssize_t i=g_interpolationCnt-1; i>=0; i--) oldipos[i] = *curipos[i];
}

EXTERN_INLINE void G_RestoreInterpolations(void)  //Stick at end of drawscreen
{
    int32_t i=g_interpolationCnt-1;

    if (--g_interpolationLock)
        return;

    for (; i>=0; i--) *curipos[i] = bakipos[i];
}

#endif

#endif


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

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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

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

#include "scriptfile.h"
#include "cache1d.h"
#include "crc32.h"

#include "duke3d.h"
#include "common_game.h"
#include "grpscan.h"

//static void process_vaca13(int32_t crcval);
static void process_vacapp15(int32_t crcval);

// custom GRP support for the startup window, file format reflects the structure below
#define GAMELISTFILE "games.list"
//    name                                     crc          size      flags                         dependency  scriptname     postprocessing
static internalgrpinfo_t const internalgrpfiles[] =
{
    //{ "Duke Nukem 3D",                         DUKE13_CRC,  26524524, GAMEFLAG_DUKE,                         0, NULL, NULL},
    //{ "Duke Nukem 3D (South Korean Censored)", DUKEKR_CRC,  26385383, GAMEFLAG_DUKE,                         0, NULL, NULL},
    { "Duke Nukem 3D: Atomic Edition",         DUKE15_CRC,  44356548, GAMEFLAG_DUKE,                         0, NULL, NULL},
    { "Duke Nukem 3D: Atomic Edition (WT)",    DUKEWT_CRC,  44356548, GAMEFLAG_DUKE,                         0, NULL, NULL},
    { "Duke Nukem 3D: Plutonium Pak",          DUKEPP_CRC,  44348015, GAMEFLAG_DUKE,                         0, NULL, NULL},
    //{ "Duke Nukem 3D Shareware 0.99",          DUKE099_CRC, 9690241,  GAMEFLAG_DUKE|GAMEFLAG_DUKEBETA,       0, NULL, NULL},
    //{ "Duke Nukem 3D Shareware 1.0",           DUKE10_CRC,  10429258, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE,      0, NULL, NULL},
    //{ "Duke Nukem 3D Shareware 1.1",           DUKE11_CRC,  10442980, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE,      0, NULL, NULL},
    //{ "Duke Nukem 3D Shareware 1.3D",          DUKESW_CRC,  11035779, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE,      0, NULL, NULL},
    //{ "Duke Nukem 3D Mac Demo",                DUKEMD_CRC,  10444391, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE,      0, NULL, NULL},
    //{ "Duke Nukem 3D MacUser Demo",            DUKEMD2_CRC, 10628573, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE,      0, NULL, NULL },
    //{ "Duke it out in D.C. (1.3D)",            DUKEDC13_CRC, 7926624, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE13_CRC, NULL, NULL},
    { "Duke it out in D.C.",                   DUKEDCPP_CRC, 8225517, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, NULL, NULL},
    { "Duke it out in D.C.",                   DUKEDC_CRC,  8410183,  GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, NULL, NULL},
    { "Duke it out in D.C.",                   (int32_t) 0x39A692BF,  8410187, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "DUKEDC.CON", NULL},
    //{ "Duke Caribbean: Life's a Beach (1.3D)", VACA13_CRC,  23559381, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE13_CRC, NULL, process_vaca13},
    { "Duke Caribbean: Life's a Beach (PPak)", VACAPP_CRC,  22551333, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKEPP_CRC, NULL, process_vacapp15},
    { "Duke Caribbean: Life's a Beach",        VACA15_CRC,  22521880, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, NULL, process_vacapp15},
    { "Duke Caribbean: Life's a Beach",        DUKECB_CRC,  22213819, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, NULL, NULL},
    { "Duke Caribbean: Life's a Beach",        (int32_t) 0x65B5F690, 22397273, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "VACATION.CON", NULL},
    { "Duke: Nuclear Winter",                  DUKENW_CRC,  16169365, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "NWINTER.CON", NULL},
    { "Duke: Nuclear Winter Demo",             (int32_t) 0xC7EFBFA9,  10965909, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "NWINTER.CON", NULL},
   // { "Duke!ZONE II (1.3D)",                   DZ2_13_CRC,  26135388, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE13_CRC, "DZ-GAME.CON", NULL},
    { "Duke!ZONE II",                          DZ2_PP_CRC,  44100411, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "DZ-GAME.CON", NULL},
    { "Duke!ZONE II",                          (int32_t) 0x1E9516F1,  3186656, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "DZ-GAME.CON", NULL},
    { "NAM",                                   NAM_CRC,     43448927, GAMEFLAG_NAM,                          0, NULL, NULL},
    { "NAPALM",                                NAPALM_CRC,  44365728, GAMEFLAG_NAM|GAMEFLAG_NAPALM,          0, NULL, NULL},
    { "WWII GI",                               WW2GI_CRC,   77939508, GAMEFLAG_WW2GI,                        0, NULL, NULL},
    { "Platoon Leader",                        PLATOONL_CRC, 37852572, GAMEFLAG_WW2GI|GAMEFLAG_ADDON,        WW2GI_CRC, "PLATOONL.DEF", NULL},
    { "Redneck Rampage",                       RR_CRC,     141174222, GAMEFLAG_RR,                           0, NULL, NULL },
    { "Redneck Rampage: Rides Again",          RRRA_CRC,   191798609, GAMEFLAG_RR|GAMEFLAG_RRRA,             0, NULL, NULL },
};

struct grpfile_t *foundgrps = NULL;
struct grpinfo_t *listgrps = NULL;
struct grpinfo_t *dn64grpinfo = NULL;

static void LoadList(const char * filename)
{
    scriptfile *script = scriptfile_fromfile(filename);

    if (!script)
        return;

    scriptfile_addsymbolvalue("GAMEFLAG_DUKE", GAMEFLAG_DUKE);
    scriptfile_addsymbolvalue("GAMEFLAG_ADDON", GAMEFLAG_DUKE|GAMEFLAG_ADDON);
    scriptfile_addsymbolvalue("GAMEFLAG_NAM", GAMEFLAG_NAM);
    scriptfile_addsymbolvalue("GAMEFLAG_NAPALM", GAMEFLAG_NAM|GAMEFLAG_NAPALM);
    scriptfile_addsymbolvalue("GAMEFLAG_RR", GAMEFLAG_RR);
    scriptfile_addsymbolvalue("GAMEFLAG_RRRA", GAMEFLAG_RR|GAMEFLAG_RRRA);
    scriptfile_addsymbolvalue("DUKE15_CRC", DUKE15_CRC);
    scriptfile_addsymbolvalue("DUKEPP_CRC", DUKEPP_CRC);
    scriptfile_addsymbolvalue("DUKE13_CRC", DUKE13_CRC);
    scriptfile_addsymbolvalue("DUKEDC13_CRC", DUKEDC13_CRC);
    scriptfile_addsymbolvalue("DUKEDCPP_CRC", DUKEDCPP_CRC);
    scriptfile_addsymbolvalue("DUKEDC_CRC", DUKEDC_CRC);
    scriptfile_addsymbolvalue("VACA13_CRC", VACA13_CRC);
    scriptfile_addsymbolvalue("VACAPP_CRC", VACAPP_CRC);
    scriptfile_addsymbolvalue("VACA15_CRC", VACA15_CRC);
    scriptfile_addsymbolvalue("DUKECB_CRC", DUKECB_CRC);
    scriptfile_addsymbolvalue("DUKENW_CRC", DUKENW_CRC);
    scriptfile_addsymbolvalue("DZ2_13_CRC", DZ2_13_CRC);
    scriptfile_addsymbolvalue("DZ2_PP_CRC", DZ2_PP_CRC);
    scriptfile_addsymbolvalue("NAM_CRC", NAM_CRC);
    scriptfile_addsymbolvalue("NAPALM_CRC", NAPALM_CRC);
    scriptfile_addsymbolvalue("WW2GI_CRC", WW2GI_CRC);

    while (!scriptfile_eof(script))
    {
        enum
        {
            T_GRPINFO,
            T_GAMENAME,
            T_CRC,
            T_SIZE,
            T_DEPCRC,
            T_SCRIPTNAME,
            T_DEFNAME,
            T_FLAGS,
            T_RTSNAME,
        };

        static const tokenlist profiletokens[] =
        {
            { "grpinfo",            T_GRPINFO },
        };

        int32_t token = getatoken(script,profiletokens,ARRAY_SIZE(profiletokens));
        switch (token)
        {
        case T_GRPINFO:
        {
            int32_t gsize = 0, gcrcval = 0, gflags = GAMEFLAG_DUKE, gdepcrc = DUKE15_CRC;
            char *gname = NULL, *gscript = NULL, *gdef = NULL;
            char *grts = NULL;
            char *grpend = NULL;

            static const tokenlist grpinfotokens[] =
            {
                { "name",           T_GAMENAME },
                { "scriptname",     T_SCRIPTNAME },
                { "defname",        T_DEFNAME },
                { "rtsname",        T_RTSNAME },
                { "crc",            T_CRC },
                { "dependency",     T_DEPCRC },
                { "size",           T_SIZE },
                { "flags",          T_FLAGS },

            };

            if (scriptfile_getbraces(script,&grpend)) break;

            while (script->textptr < grpend)
            {
                int32_t token = getatoken(script,grpinfotokens,ARRAY_SIZE(grpinfotokens));

                switch (token)
                {
                case T_GAMENAME:
                    scriptfile_getstring(script,&gname); break;
                case T_SCRIPTNAME:
                    scriptfile_getstring(script,&gscript); break;
                case T_DEFNAME:
                    scriptfile_getstring(script,&gdef); break;
                case T_RTSNAME:
                    scriptfile_getstring(script,&grts); break;

                case T_FLAGS:
                    scriptfile_getsymbol(script,&gflags); gflags &= GAMEFLAGMASK; break;
                case T_DEPCRC:
                    scriptfile_getsymbol(script,&gdepcrc); break;
                case T_CRC:
                    scriptfile_getsymbol(script,&gcrcval); break;
                case T_SIZE:
                    scriptfile_getnumber(script,&gsize); break;
                default:
                    break;
                }

                grpinfo_t * const fg = (grpinfo_t *)Xcalloc(1, sizeof(grpinfo_t));
                fg->next = listgrps;
                listgrps = fg;

                if (gname)
                    fg->name = Xstrdup(gname);

                fg->size = gsize;
                fg->crcval = gcrcval;
                fg->dependency = gdepcrc;
                fg->game = gflags;

                if (gscript)
                    fg->scriptname = dup_filename(gscript);

                if (gdef)
                    fg->defname = dup_filename(gdef);

                if (grts)
                    fg->rtsname = dup_filename(grts);
            }
            break;
        }

        default:
            break;
        }
    }

    scriptfile_close(script);
    scriptfile_clearsymbols();
}

static void LoadGameList(void)
{
    for (size_t i = 0; i < ARRAY_SIZE(internalgrpfiles); i++)
    {
        grpinfo_t * const fg = (grpinfo_t *)Xcalloc(1, sizeof(grpinfo_t));

        fg->name = Xstrdup(internalgrpfiles[i].name);
        fg->crcval = internalgrpfiles[i].crcval;
        fg->size = internalgrpfiles[i].size;
        fg->game = internalgrpfiles[i].game;
        fg->dependency = internalgrpfiles[i].dependency;

        if (internalgrpfiles[i].scriptname)
            fg->scriptname = dup_filename(internalgrpfiles[i].scriptname);

        fg->postprocessing = internalgrpfiles[i].postprocessing;

        fg->next = listgrps;
        listgrps = fg;
    }

    BUILDVFS_FIND_REC * const srch = klistpath("/", "*.grpinfo", BUILDVFS_FIND_FILE);

    for (BUILDVFS_FIND_REC *sidx = srch; sidx; sidx = sidx->next)
        LoadList(sidx->name);

    dn64grpinfo = (grpinfo_t *)Xcalloc(1, sizeof(grpinfo_t));

    dn64grpinfo->name = Xstrdup("Duke Nukem 64");
    dn64grpinfo->crcval = 0;
    dn64grpinfo->size = -1;
    dn64grpinfo->game = GAMEFLAG_DUKE | GAMEFLAG_REALITY;
    dn64grpinfo->dependency = 0;
    dn64grpinfo->scriptname = dup_filename("duke64.def");
    dn64grpinfo->postprocessing = NULL;
    dn64grpinfo->next = listgrps;
    listgrps = dn64grpinfo;

    klistfree(srch);
}

static void FreeGameList(void)
{
    while (listgrps)
    {
        Xfree(listgrps->name);
        Xfree(listgrps->scriptname);
        Xfree(listgrps->defname);
        Xfree(listgrps->rtsname);

        grpinfo_t * const fg = listgrps->next;
        Xfree(listgrps);
        listgrps = fg;
    }
}

#define GRPCACHEFILE "grpfiles.cache"
static struct grpcache
{
    struct grpcache *next;
    int32_t size;
    int32_t mtime;
    int32_t crcval;
    char name[BMAX_PATH];
}
*grpcache = NULL, *usedgrpcache = NULL;

static int32_t LoadGroupsCache(void)
{
    struct grpcache *fg;

    int32_t fsize, fmtime, fcrcval;
    char *fname;

    scriptfile *script;

    script = scriptfile_fromfile(GRPCACHEFILE);
    if (!script) return -1;

    while (!scriptfile_eof(script))
    {
        if (scriptfile_getstring(script, &fname)) break;    // filename
        if (scriptfile_getnumber(script, &fsize)) break;    // filesize
        if (scriptfile_getnumber(script, &fmtime)) break;   // modification time
        if (scriptfile_getnumber(script, &fcrcval)) break;  // crc checksum

        fg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
        fg->next = grpcache;
        grpcache = fg;

        Bstrncpy(fg->name, fname, BMAX_PATH);
        fg->size = fsize;
        fg->mtime = fmtime;
        fg->crcval = fcrcval;
    }

    scriptfile_close(script);
    return 0;
}

static void FreeGroupsCache(void)
{
    while (grpcache)
    {
        struct grpcache * const fg = grpcache->next;
        Xfree(grpcache);
        grpcache = fg;
    }
}

static void RemoveGroup(grpfile_t *igrp)
{
    for (grpfile_t *prev = NULL, *grp = foundgrps; grp; grp=grp->next)
    {
        if (grp == igrp)
        {
            if (grp == foundgrps)
                foundgrps = grp->next;
            else
                prev->next = grp->next;

            Xfree((char *)grp->filename);
            Xfree(grp);

            return;
        }

        prev = grp;
    }
}

grpfile_t * FindGroup(int32_t crcval)
{
    grpfile_t *grp;

    for (grp = foundgrps; grp; grp=grp->next)
    {
        if (grp->type->crcval == crcval)
            return grp;
    }

    return NULL;
}

static grpinfo_t const * FindGrpInfo(int32_t crcval, int32_t size)
{
    grpinfo_t *grpinfo;

    for (grpinfo = listgrps; grpinfo; grpinfo=grpinfo->next)
    {
        if (grpinfo->crcval == crcval && grpinfo->size == size)
            return grpinfo;
    }

    return NULL;
}

static void ProcessGroups(BUILDVFS_FIND_REC *srch)
{
    BUILDVFS_FIND_REC *sidx;
    struct grpcache *fg, *fgg;
    char *fn;
    struct Bstat st;

#define BUFFER_SIZE (1024 * 1024 * 8)
    uint8_t *buf = (uint8_t *)Xmalloc(BUFFER_SIZE);

    for (sidx = srch; sidx; sidx = sidx->next)
    {
        for (fg = grpcache; fg; fg = fg->next)
        {
            if (!Bstrcmp(fg->name, sidx->name)) break;
        }

        if (fg)
        {
            if (findfrompath(sidx->name, &fn)) continue; // failed to resolve the filename
            if (Bstat(fn, &st))
            {
                Xfree(fn);
                continue;
            } // failed to stat the file
            Xfree(fn);
            if (fg->size == (int32_t)st.st_size && fg->mtime == (int32_t)st.st_mtime)
            {
                grpinfo_t const * const grptype = FindGrpInfo(fg->crcval, fg->size);
                if (grptype)
                {
                    grpfile_t * const grp = (grpfile_t *)Xcalloc(1, sizeof(grpfile_t));
                    grp->filename = Xstrdup(sidx->name);
                    grp->type = grptype;
                    grp->next = foundgrps;
                    foundgrps = grp;
                }

                fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
                strcpy(fgg->name, fg->name);
                fgg->size = fg->size;
                fgg->mtime = fg->mtime;
                fgg->crcval = fg->crcval;
                fgg->next = usedgrpcache;
                usedgrpcache = fgg;
                continue;
            }
        }

        {
            int32_t b, fh;
            int32_t crcval = 0;

            fh = openfrompath(sidx->name, BO_RDONLY|BO_BINARY, BS_IREAD);
            if (fh < 0) continue;
            if (Bfstat(fh, &st)) continue;

            initprintf(" Checksumming %s...", sidx->name);
            do
            {
                b = read(fh, buf, BUFFER_SIZE);
                if (b > 0) crcval = Bcrc32((uint8_t *)buf, b, crcval);
            }
            while (b == BUFFER_SIZE);
            close(fh);
            initprintf(" Done\n");

            grpinfo_t const * const grptype = FindGrpInfo(crcval, st.st_size);
            if (grptype)
            {
                grpfile_t * const grp = (grpfile_t *)Xcalloc(1, sizeof(grpfile_t));
                grp->filename = Xstrdup(sidx->name);
                grp->type = grptype;
                grp->next = foundgrps;
                foundgrps = grp;
            }

            fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
            Bstrncpy(fgg->name, sidx->name, BMAX_PATH);
            fgg->size = st.st_size;
            fgg->mtime = st.st_mtime;
            fgg->crcval = crcval;
            fgg->next = usedgrpcache;
            usedgrpcache = fgg;
        }
    }

    Xfree(buf);
}

static void ProcessDN64Groups(void)
{

    static char const *n64extensions[] =
    {
        "*.z64",
        "*.n64",
        "*.v64",
    };

    for (size_t i = 0; i < ARRAY_SIZE(n64extensions); i++)
    {
        struct Bstat st;
        BUILDVFS_FIND_REC *srch = klistpath("/", n64extensions[i], BUILDVFS_FIND_FILE);
        for (BUILDVFS_FIND_REC *sidx = srch; sidx; sidx = sidx->next)
        {
            char title[20];
            char country[3];
            int32_t fh = openfrompath(sidx->name, BO_RDONLY|BO_BINARY, BS_IREAD);
            if (fh < 0) continue;
            if (fstat(fh, &st)) continue;
            if (st.st_size != 0x800000)
            {
                close(fh);
                continue;
            }
            lseek(fh, 32, SEEK_SET);
            read(fh, title, 20);
            lseek(fh, 61, SEEK_SET);
            read(fh, &country, 3);
            close(fh);
            if ((!memcmp(title, "DUKE NUKEM", 10) && (country[1] == 'E' || country[1] == 'P'))
             || (!memcmp(title, "UDEKN KUME", 10) && (country[2] == 'E' || country[0] == 'P'))
             || (!memcmp(title, "EKUDKUN ", 8) && (country[0] == 'E' || country[0] == 'P')))
            {
                grpfile_t * const grp = (grpfile_t *)Xcalloc(1, sizeof(grpfile_t));
                grp->filename = Xstrdup(sidx->name);
                grp->type = dn64grpinfo;
                grp->next = foundgrps;
                foundgrps = grp;
            }
            else
                continue;
        }
        klistfree(srch);
    }
}

int32_t ScanGroups(void)
{
    struct grpcache *fg, *fgg;

    initprintf("Searching for game data...\n");

    LoadGameList();
    LoadGroupsCache();

    static char const *extensions[] =
    {
        "*.grp",
        "*.ssi",
        "*.dat",
    };

    for (char const *extension : extensions)
    {
        BUILDVFS_FIND_REC *srch = klistpath("/", extension, BUILDVFS_FIND_FILE);
        ProcessGroups(srch);
        klistfree(srch);
    }

    ProcessDN64Groups();

    FreeGroupsCache();

    for (grpfile_t *grp = foundgrps; grp; grp=grp->next)
    {
        if (grp->type->dependency)
        {
            if (FindGroup(grp->type->dependency) == NULL) // couldn't find dependency
            {
                //initprintf("removing %s\n", grp->name);
                RemoveGroup(grp);
                grp = foundgrps;
                // start from the beginning so we can remove anything that depended on this grp
                continue;
            }
        }
    }

    if (usedgrpcache)
    {
        int32_t i = 0;
        FILE *fp;
        fp = fopen(GRPCACHEFILE, "wt");
        if (fp)
        {
            for (fg = usedgrpcache; fg; fg=fgg)
            {
                fgg = fg->next;
                fprintf(fp, "\"%s\" %d %d %d\n", fg->name, fg->size, fg->mtime, fg->crcval);
                Xfree(fg);
                i++;
            }
            fclose(fp);
        }
//        initprintf("Found %d recognized GRP %s.\n",i,i>1?"files":"file");

        return 0;
    }

    initprintf("Found no recognized game data!\n");

    return 0;
}

void FreeGroups(void)
{
    while (foundgrps)
    {
        Xfree((char *)foundgrps->filename);
        grpfile_t * const fg = foundgrps->next;
        Xfree(foundgrps);
        foundgrps = fg;
    }

    FreeGameList();
}

static void process_vacapp15(int32_t crcval)
{
    krename(crcval, 5, "DEFS.CON");
    krename(crcval, 6, "GAME.CON");
    krename(crcval, 7, "USER.CON");
    krename(crcval, 8, "DEMO1.DMO");
    krename(crcval, 9, "DEMO2.DMO");
    krename(crcval, 10, "DEMO3.DMO");

    initgroupfile("VACATION.PRG");
}


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

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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

#ifndef grpscan_h_
#define grpscan_h_

#ifdef __cplusplus
extern "C" {
#endif

#define MAXLISTNAMELEN 32

// List of internally-known GRP files
#define DUKE13_CRC  (int32_t)0xBBC9CE44
#define DUKEKR_CRC  (int32_t)0xAA4F6A40
#define DUKE15_CRC  (int32_t)0xFD3DCFF1
#define DUKEPP_CRC  (int32_t)0xF514A6AC
#define DUKEWT_CRC  (int32_t)0x982AFE4A
#define DUKE099_CRC (int32_t)0x02F18900
#define DUKE10_CRC  (int32_t)0xA28AA589
#define DUKE11_CRC  (int32_t)0x912E1E8D
#define DUKESW_CRC  (int32_t)0x983AD923
#define DUKEMD_CRC  (int32_t)0xC5F71561
#define DUKEMD2_CRC  (int32_t)0x73A15EE7
#define DUKEDC13_CRC (int32_t)0xA9242158
#define DUKEDCPP_CRC (int32_t)0xB79D997F
#define DUKEDC_CRC  (int32_t)0xA8CF80DA
#define VACA13_CRC  (int32_t)0x4A2DBB62
#define VACAPP_CRC  (int32_t)0x2F4FCCEE
#define VACA15_CRC  (int32_t)0xB62B42FD
#define DUKECB_CRC  (int32_t)0x18F01C5B
#define DUKENW_CRC  (int32_t)0xF1CAE8E4
#define DZ2_13_CRC  (int32_t)0x82C1B47F
#define DZ2_PP_CRC  (int32_t)0x7FB6117C
#define NAM_CRC     (int32_t)0x75C1F07B
#define NAPALM_CRC  (int32_t)0x3DE1589A
#define WW2GI_CRC   (int32_t)0x907B82BF
#define PLATOONL_CRC (int32_t)0xD1ED8C0C
#define RR_CRC      (int32_t)0x19D9BC79
#define RRRA_CRC    (int32_t)0x958018C6

enum addon_t {
    ADDON_NONE,
    ADDON_DUKEDC,
    ADDON_NWINTER,
    ADDON_CARIBBEAN,
    NUMADDONS
};

typedef struct internalgrpinfo_t {
    char const *name;
    int32_t const crcval;
    int32_t const size;
    int32_t const game;
    int32_t const dependency;
    char const *scriptname;
    void(*postprocessing)(int32_t);
} internalgrpinfo_t;

typedef struct grpinfo_t {
	char *name;
	int32_t crcval;
	int32_t size;
	int32_t game;
    int32_t dependency;
    char *scriptname;
    char *defname;
    char *rtsname;
    void (*postprocessing)(int32_t);
	struct grpinfo_t *next;
} grpinfo_t;

typedef struct grpfile_t {
	char *filename;
	struct grpinfo_t const *type;
	struct grpfile_t *next;
} grpfile_t;

extern grpfile_t *foundgrps;
extern grpinfo_t *listgrps;

extern grpfile_t * FindGroup(int32_t crcval);

int32_t ScanGroups(void);
void FreeGroups(void);

#ifdef __cplusplus
}
#endif

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2015 EDuke32 developers and contributors
Copyright (C) 2015 Voidpoint, LLC

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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

#include "compat.h"
#include "sdl_inc.h"
#include "baselayer.h"
#include "keys.h"
#include "duke3d.h"
#include "common_game.h"
#include "osd.h"
#include "player.h"
#include "game.h"
#include "build.h"
#include "anim.h"
#include "player.h"

#include "keyboard.h"
#include "control.h"
#include "_control.h"

#include "menus.h"

#ifdef __cplusplus
extern "C" {
#endif
extern int SDL_SendKeyboardKey(Uint8 state, SDL_Scancode scancode);
extern int SDL_SendKeyboardText(const char *text);
extern int SDL_SendMouseMotion(SDL_Window * window, Uint32 mouseID, int relative, int x, int y);
extern int SDL_SendMouseButton(SDL_Window * window, Uint32 mouseID, Uint8 state, Uint8 button);

#ifdef __cplusplus
}
#endif

#include "in_android.h"
#include <android/log.h>

#if defined __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO,"DUKE", __VA_ARGS__))

static char sdl_text[2];

droidinput_t droidinput;

void AndroidTimer(int tics) { G_InitTimer(tics);  }

int AndroidKeyEvent(int state, int code,int unicode)
{
    LOGI("AndroidKeyEvent %d %d %d",state,(SDL_Scancode)code,unicode);
    SDL_SendKeyboardKey(state ? SDL_PRESSED : SDL_RELEASED, (SDL_Scancode)code);
    SDL_EventState(SDL_TEXTINPUT, SDL_ENABLE);

    // if (code == 42)
    //    unicode = 42;

    if (state)
    {
        //if (unicode < 128)
        {
            sdl_text[0] = unicode;
            sdl_text[1] = 0;

            int posted = SDL_SendKeyboardText((const char*)sdl_text);
            LOGI("posted = %d",posted);
        }

        if (state == 2)
            AndroidKeyEvent(0, code, unicode);
    }

    return 0;
}

void AndroidMouseMenu(float x,float y)
{
    SDL_SendMouseMotion(NULL,0,0,x,y);
}

void AndroidMouseMenuButton(int state,int button)
{
    SDL_SendMouseButton(NULL, SDL_TOUCH_MOUSEID, state?SDL_PRESSED:SDL_RELEASED, SDL_BUTTON_LEFT);
}

void changeActionState(int state, int action)
{
    if (state)
    {
        //BUTTONSET(action,1);
        droidinput.functionSticky  |= ((uint64_t)1<<((uint64_t)(action)));
        droidinput.functionHeld    |= ((uint64_t)1<<((uint64_t)(action)));
        return;
    }

    //BUTTONCLEAR(action);
    droidinput.functionHeld  &= ~((uint64_t) 1<<((uint64_t) (action)));
}

void AndroidAction(int state, int action)
{
    LOGI("AndroidAction action = %d, state = %d", action, state);

    if (action >= MENU_UP && action <= MENU_BACK)
    {
        int const sdl_code [] = { SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT,
                SDL_SCANCODE_RIGHT, SDL_SCANCODE_RETURN, SDL_SCANCODE_ESCAPE };
        AndroidKeyEvent(state, sdl_code[action-MENU_UP], 0);
        return;
    }
    else
    {
        //if (AndroidRead(READ_SCREEN_MODE) != TOUCH_SCREEN_GAME) //If not in game don't do any of this
        ///     return;

        //Special toggle for crouch, NOT when using jetpack or in water
        if (droidinput.toggleCrouch)
        {
            int lotag = sector[g_player[myconnectindex].ps->cursectnum].lotag;

            if (droidinput.crouchToggleState && (lotag == ST_2_UNDERWATER || lotag == ST_1_ABOVE_WATER))
            {
                droidinput.crouchToggleState = 0;
                if (action == gamefunc_Crouch)
                    state = 0;
                else AndroidAction(0, gamefunc_Crouch);
            }

            if (action == gamefunc_Crouch)
            {
                if (!g_player[myconnectindex].ps->jetpack_on && g_player[myconnectindex].ps->on_ground &&
                    lotag != ST_2_UNDERWATER && lotag != ST_1_ABOVE_WATER)
                {
                    if (state)
                        droidinput.crouchToggleState = !droidinput.crouchToggleState;

                    state = droidinput.crouchToggleState;
                }
            }

        }

        //Check if jumping while crouched
        if (action == gamefunc_Jump)
        {
            if (droidinput.crouchToggleState)
            {
                droidinput.crouchToggleState = 0;
                changeActionState(0, gamefunc_Crouch);
            }
            else
                changeActionState(state, action);
        }
        else
            changeActionState(state, action);

        if (state == 2)
            AndroidAction(0, action);

        // LOGI("AndroidAction state = 0x%016llX", CONTROL_ButtonState);
    }
}

int const deadRegion = 0.3;

float analogCalibrate(float v)
{
    float rv = 0;

    if (v > deadRegion) rv = (v - deadRegion) * (v - deadRegion);
    else if (-v > deadRegion) rv = -(-v - deadRegion) * (-v - deadRegion);

    return rv;
}

//Need these NAN check as not cumulative.
void AndroidMove(float fwd, float strafe)
{
    if (!ud.auto_run)
    {
        fwd *= 0.5f;
        strafe *= 0.5f;
    }

    if (!isnan(fwd))
        droidinput.forwardmove = fclamp2(analogCalibrate(fwd), -1.f, 1.f);

    if (!isnan(strafe))
        droidinput.sidemove    = fclamp2(analogCalibrate(strafe),    -1.f, 1.f);
}

void AndroidLook(float yaw, float pitch)
{
    droidinput.pitch += pitch;
    droidinput.yaw   += yaw;
}

void AndroidLookJoystick(float yaw, float pitch)
{
    if (!isnan(pitch))
        droidinput.pitch_joystick = analogCalibrate(pitch);

    if (!isnan(yaw))
        droidinput.yaw_joystick   = analogCalibrate(yaw);
}

void AndroidOSD(const char * cmd)
{
    OSD_Dispatch(cmd);
}

int consoleShown = 0;
void AndroidSetConsoleShown(int onf)
{
    consoleShown = onf;
}

extern int inExtraScreens; //In game.c
int32_t AndroidRead(portableread_t r)
{
    int32_t rv;

    switch (r)
    {
    case R_TOUCH_MODE:
        if (g_animPtr || inExtraScreens)
            rv = TOUCH_SCREEN_BLANK_TAP;
        else if (consoleShown)
            rv = TOUCH_SCREEN_CONSOLE;
        else if ((g_player[myconnectindex].ps->gm & MODE_MENU) == MODE_MENU && g_currentMenu != MENU_MAIN)
            rv = (m_currentMenu->type == Verify && totalclock > (m_animation.length + m_animation.start)) ? TOUCH_SCREEN_YES_NO : TOUCH_SCREEN_MENU;
        else if ((g_player[myconnectindex].ps->gm & MODE_MENU) == MODE_MENU && g_currentMenu == MENU_MAIN)
            rv = TOUCH_SCREEN_MENU_NOBACK;
/*
        else if (ud.overhead_on == 2)
            rv = TOUCH_SCREEN_AUTOMAP;
*/
        else if ((g_player[myconnectindex].ps->gm & MODE_GAME))
            if (AndroidRead(R_PLAYER_DEAD_FLAG))
                rv = TOUCH_SCREEN_BLANK_TAP;
            else
                rv = TOUCH_SCREEN_GAME;
        else
            rv = TOUCH_SCREEN_BLANK_TAP;
         break;
    case R_PLAYER_GOTWEAPON:
        rv = g_player[myconnectindex].ps->gotweapon; break;
    case R_UD_OVERHEAD_ON:
        rv = 0; break;//ud.overhead_on != 0;  break;// ud.overhead_on ranges from 0-2
    case R_UD_SCROLLMODE:
        rv = ud.scrollmode; break;
    case R_PLAYER_LASTWEAPON:
        rv = droidinput.lastWeapon;
        if ((unsigned)rv < MAX_WEAPONS && !g_player[myconnectindex].ps->ammo_amount[rv])
            rv = -1;
        break;
    case R_GAME_PAUSED:
        rv = ud.pause_on != 0; break;
    case R_PLAYER_DEAD_FLAG:
        rv = g_player[myconnectindex].ps->dead_flag; break;
    case R_PLAYER_INV_AMOUNT:
        rv = 0;
        for (bssize_t i = 0; i < GET_MAX; i++)
        {
            if (g_player[myconnectindex].ps->inv_amount[i])
                rv += (1 << i);
        }
        break;
    case R_SOMETHINGONPLAYER:
        rv = g_player[myconnectindex].ps->somethingonplayer != -1;
        break;
    default:
        rv = 0; break;
    }

    return rv;
}

static float map_zoom,map_dx,map_dy = 0;

void AndroidAutomapControl(float zoom,float dx,float dy)
{
    map_zoom += zoom;
    map_dx += dx;
    map_dy += dy;
}

///This stuff is called from the game/engine

extern void  CONTROL_Android_ScrollMap(int32_t *angle,int32_t *x, int32_t *y, uint16_t *zoom )
{

    *x += ((int)(map_dx * -30000)*sintable[(512+2048-*angle)&2047])>>14;
    *y += ((int)(map_dy * -30000)*sintable[(512+1024-512-*angle)&2047])>>14;

//    *zoom += map_zoom * 2000;
    //*angle = 0;
    map_dx = map_dy = map_zoom = 0;
}

void CONTROL_Android_SetLastWeapon(int w)
{
    droidinput.lastWeapon = w;
}

void CONTROL_Android_ClearButton(int32_t whichbutton)
{
    droidinput.functionHeld  &= ~((uint64_t)1<<((uint64_t)(whichbutton)));
}

void CONTROL_Android_PollDevices(ControlInfo *info)
{
    //LOGI("CONTROL_Android_PollDevices %f %f",forwardmove,sidemove);
    //LOGI("CONTROL_Android_PollDevices %f %f",droidinput.pitch,droidinput.yaw);

    info->dz = (int32_t)nearbyintf(-droidinput.forwardmove * ANDROIDMOVEFACTOR);
    info->dx = (int32_t)nearbyintf(droidinput.sidemove * ANDROIDMOVEFACTOR) >> 5;
    info->dpitch =
    (int32_t)nearbyintf(droidinput.pitch * ANDROIDLOOKFACTOR + droidinput.pitch_joystick * ANDROIDPITCHFACTORJOYSTICK);
    info->dyaw =
    (int32_t)nearbyintf(-droidinput.yaw * ANDROIDLOOKFACTOR - droidinput.yaw_joystick * ANDROIDYAWFACTORJOYSTICK);

    droidinput.pitch = droidinput.yaw = 0.f;
    CONTROL_ButtonState = droidinput.functionSticky | droidinput.functionHeld;
    droidinput.functionSticky = 0;

    //LOGI("poll state = 0x%016llX",CONTROL_ButtonState);
}

#if defined __GNUC__
# pragma GCC diagnostic pop
#endif


code:
#include "function.h"

#ifdef __cplusplus
extern "C"
{
#endif

#define MENU_UP                  0x200
#define MENU_DOWN                0x201
#define MENU_LEFT                0x202
#define MENU_RIGHT               0x203
#define MENU_SELECT              0x204
#define MENU_BACK                0x205

#define KEY_QUICK_CMD    0x1005
#define KEY_SHOW_KBRD    0x1008
#define KEY_SHOW_INVEN    0x1009
#define KEY_QUICK_SAVE    0x100A
#define KEY_QUICK_LOAD    0x100B

#define KEY_QUICK_KEY1    0x1011
#define KEY_QUICK_KEY2    0x1012
#define KEY_QUICK_KEY3    0x1013
#define KEY_QUICK_KEY4    0x1014

// #define BUTTONSET(x,value) (CONTROL_ButtonState |= ((uint64_t)value<<((uint64_t)(x))))
// #define BUTTONCLEAR(x) (CONTROL_ButtonState &= ~((uint64_t)1<<((uint64_t)(x))))

#define PRECISIONSHOOTFACTOR        0.3f

// where do these numbers come from?
#define ANDROIDMOVEFACTOR           6400
#define ANDROIDLOOKFACTOR          1600

#define ANDROIDPITCHFACTORJOYSTICK          2000
#define ANDROIDYAWFACTORJOYSTICK            4000

typedef enum {
    R_TOUCH_MODE,
    R_PLAYER_GOTWEAPON,
    R_UD_OVERHEAD_ON,
    R_UD_SCROLLMODE,
    R_PLAYER_LASTWEAPON,
    R_GAME_PAUSED,
    R_PLAYER_DEAD_FLAG,
    R_PLAYER_INV_AMOUNT,
    R_SOMETHINGONPLAYER
} portableread_t;

typedef enum {
    TOUCH_SCREEN_BLANK, //Nothing on screen (not used)
    TOUCH_SCREEN_BLANK_TAP, //One button filling screen with no graphic, tap to send Enter key
    TOUCH_SCREEN_YES_NO, //Yes/No buttons on screen, sends Enter or Esc
    TOUCH_SCREEN_MENU, //Normal menu
    TOUCH_SCREEN_MENU_NOBACK, // menu without back button
    TOUCH_SCREEN_GAME, //Normal game screen
    TOUCH_SCREEN_AUTOMAP, //When auto map is up (not used yet)
    TOUCH_SCREEN_CONSOLE //When Console is up
} touchscreemode_t;

typedef struct
{
    int32_t crouchToggleState;
    int32_t lastWeapon;
    int32_t toggleCrouch;
    int32_t quickSelectWeapon;

    uint64_t functionSticky; //To let at least one tick
    uint64_t functionHeld;

    int32_t left_double_action;
    int32_t right_double_action;

    int32_t invertLook, hideStick;

    double pitch, yaw;
    double pitch_joystick, yaw_joystick;
    float forwardmove, sidemove;

    // set by configuration UI
    float strafe_sens, forward_sens;
    float pitch_sens, yaw_sens;

    float gameControlsAlpha;
} droidinput_t;

typedef struct
{
    int32_t audio_sample_rate;
    int32_t audio_buffer_size;
    uint16_t screen_width, screen_height;
} droidsysinfo_t;

extern droidinput_t droidinput;
extern droidsysinfo_t droidinfo;

void AndroidTimer(int tics);
int AndroidKeyEvent(int state, int code, int unicode);
int AndroidRead(portableread_t r);

void AndroidAction(int state, int action);

void AndroidMouseMenu(float x,float y);
void AndroidMouseMenuButton(int state,int button);

void AndroidMove(float fwd, float strafe);
void AndroidLook(float yaw, float pitch);
void AndroidLookJoystick(float yaw, float pitch);
void AndroidOSD(const char * cmd);

void AndroidAutomapControl(float zoom,float dx,float dy);

void AndroidShowKeyboard(int onf);

void AndroidToggleButtonEditor(void);
#ifdef __cplusplus
}
#endif