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 player_h_
#define player_h_

#include "build.h"
#include "inv.h"

#ifdef __cplusplus
extern "C" {
#endif

extern int32_t g_mostConcurrentPlayers;

#define MOVEFIFOSIZ                 2

#define NAM_GRENADE_LIFETIME        120
#define NAM_GRENADE_LIFETIME_VAR    30

#define HORIZ_MIN                   (-99)
#define HORIZ_MAX                   299
#define AUTO_AIM_ANGLE              48
#define PHEIGHT                     (38<<8)
#define PMINHEIGHT                  1024 // this is NOT the value I wanted here, but Duke It Out in DC's shitty vents said otherwise

#define PCRACKTIME                  777

#define TRIPBOMB_TRIPWIRE       0x00000001
#define TRIPBOMB_TIMER          0x00000002

#define PIPEBOMB_REMOTE         0x00000001
#define PIPEBOMB_TIMER          0x00000002

#define WEAPON_POS_LOWER            (-9)
#define WEAPON_POS_RAISE            10
#define WEAPON_POS_START             6

enum weaponuniqhudid_t
{
    W_ACCESSCARD,
    W_CHAINGUN_BOTTOM,
    W_CHAINGUN_HACK,
    W_CHAINGUN_TOP,
    W_DEVISTATOR_LEFT,
    W_DEVISTATOR_RIGHT,
    W_DUKENUKEM,
    W_FIST,
    W_FIST2,
    W_FREEZE_BASE,
    W_FREEZE_TOP,
    W_HANDBOMB,
    W_HANDREMOTE,
    W_KNEE,
    W_KNEE2,
    W_KNUCKLES,
    W_LOOGIE,
    W_LOOGIE_END = W_LOOGIE + 63,
    W_PISTOL,
    W_PISTOL_CLIP,
    W_PISTOL_HAND,
    W_PLUTOPAK,
    W_RPG,
    W_RPG_MUZZLE,
    W_SHOTGUN,
    W_SHOTGUN_MUZZLE,
    W_SHRINKER,
    W_SHRINKER_CRYSTAL,
    W_THREEDEE,
    W_TIP,
    W_TRIPBOMB,
    W_TRIPBOMB_LEFTHAND,
    W_TRIPBOMB_RIGHTHAND,

    W_END,
};

EDUKE32_STATIC_ASSERT(W_END < MAXUNIQHUDID);

enum weaponflags_t {
    WEAPON_SPAWNTYPE1           = 0x00000000, // just spawn
    WEAPON_HOLSTER_CLEARS_CLIP  = 0x00000001, // 'holstering' clears the current clip
    WEAPON_GLOWS                = 0x00000002, // weapon 'glows' (shrinker and grower)
    WEAPON_AUTOMATIC            = 0x00000004, // automatic fire (continues while 'fire' is held down
    WEAPON_FIREEVERYOTHER       = 0x00000008, // during 'hold time' fire every frame
    WEAPON_FIREEVERYTHIRD       = 0x00000010, // during 'hold time' fire every third frame
    WEAPON_RANDOMRESTART        = 0x00000020, // restart for automatic is 'randomized' by RND 3
    WEAPON_AMMOPERSHOT          = 0x00000040, // uses ammo for each shot (for automatic)
    WEAPON_BOMB_TRIGGER         = 0x00000080, // weapon is the 'bomb' trigger
    WEAPON_NOVISIBLE            = 0x00000100, // weapon use does not cause user to become 'visible'
    WEAPON_THROWIT              = 0x00000200, // weapon 'throws' the 'shoots' item...
    WEAPON_CHECKATRELOAD        = 0x00000400, // check weapon availability at 'reload' time
    WEAPON_STANDSTILL           = 0x00000800, // player stops jumping before actual fire (like tripbomb in duke)
    WEAPON_SPAWNTYPE2           = 0x00001000, // spawn like shotgun shells
    WEAPON_SPAWNTYPE3           = 0x00002000, // spawn like chaingun shells
    WEAPON_SEMIAUTO             = 0x00004000, // cancel button press after each shot
    WEAPON_RELOAD_TIMING        = 0x00008000, // special casing for pistol reload sounds
    WEAPON_RESET                = 0x00010000  // cycle weapon back to frame 1 if fire is held, 0 if not
};

enum gamemode_t {
    MODE_MENU                   = 0x00000001,
    MODE_DEMO                   = 0x00000002,
    MODE_GAME                   = 0x00000004,
    MODE_EOL                    = 0x00000008,
    MODE_TYPE                   = 0x00000010,
    MODE_RESTART                = 0x00000020,
    MODE_SENDTOWHOM             = 0x00000040,
    MODE_NEWGAME                = 0x00000080,
};

// Player Actions.
enum playeraction_t {
    pstanding                   = 0x00000001,
    pwalking                    = 0x00000002,
    prunning                    = 0x00000004,
    pducking                    = 0x00000008,
    pfalling                    = 0x00000010,
    pjumping                    = 0x00000020,
    phigher                     = 0x00000040,
    pwalkingback                = 0x00000080,
    prunningback                = 0x00000100,
    pkicking                    = 0x00000200,
    pshrunk                     = 0x00000400,
    pjetpack                    = 0x00000800,
    ponsteroids                 = 0x00001000,
    ponground                   = 0x00002000,
    palive                      = 0x00004000,
    pdead                       = 0x00008000,
    pfacing                     = 0x00010000
};

#define AM_MOUSE 1
#define AM_CENTERING 2
#define AM_AIMASSIST 4

typedef struct {
    union { struct { int32_t x, y, z; }; vec3_t xyz; vec2_t xy; };
    int16_t ang, sect;
} playerspawn_t;

typedef struct {
    int16_t got_access, last_extra, inv_amount[GET_MAX], curr_weapon, holoduke_on;
    int16_t last_weapon, weapon_pos, kickback_pic;
    int16_t ammo_amount[MAX_WEAPONS];
    uint16_t frag[MAXPLAYERS];
    uint16_t gotweapon;
    char inven_icon, jetpack_on, heat_on;
} DukeStatus_t;

#pragma pack(push,1)
typedef struct {
    uint32_t bits;
    int16_t fvel, svel;
    fix16_t q16avel, q16horz;
    uint32_t extbits;
} input_t;

// XXX: r1625 changed a lot types here, among others
//  * int32_t --> int16_t
//  * int16_t --> int8_t
//  * char --> int8_t
// Need to carefully think about implications!
// TODO: rearrange this if the opportunity arises!
typedef struct {
    vec3_t pos, opos;
    vec3_t vel, npos;
    vec2_t bobpos, fric;

    fix16_t q16horiz, q16horizoff, oq16horiz, oq16horizoff;
    fix16_t q16ang, oq16ang, q16angvel;

    int32_t truefz, truecz, player_par;
    int32_t runspeed, max_player_health, max_shield_amount;
    int32_t autostep, autostep_sbw;

    uint32_t interface_toggle;

    palette_t pals;

    uint16_t max_actors_killed, actors_killed;
    uint16_t gotweapon, zoom;

    uint16_t frag, fraggedself;

    vec2_16_t loogie[6];
    int16_t filler[102]; // jesus fucking christ

    int16_t olook_ang;
    int16_t floorzoffset, spritezoffset, minwaterzdist, waterzoffset, shrunkzoffset;
    int16_t crouchzincrement, crouchspeedmodifier, swimspeedmodifier;
    int16_t swimzincrement, minswimzvel, maxswimzvel;
    int16_t jetpackzincrement;

    int16_t gravity;
    int16_t sbs, sound_pitch;

    int16_t cursectnum, look_ang, last_extra, subweapon;
    int16_t max_ammo_amount[MAX_WEAPONS], ammo_amount[MAX_WEAPONS], inv_amount[GET_MAX];
    int16_t wackedbyactor, pyoff, opyoff;

    int16_t newowner, jumping_counter, airleft;
    int16_t fta, ftq, access_wallnum, access_spritenum;
    int16_t got_access, weapon_ang, visibility;
    int16_t somethingonplayer, on_crane, i, parallax_sectnum;
    int16_t random_club_frame, one_eighty_count;
    int16_t dummyplayersprite, extra_extra8;
    int16_t actorsqu, timebeforeexit, customexitsound, last_pissed_time;

    int16_t weaprecs[MAX_WEAPONS], weapon_sway, crack_time, bobcounter;

    int16_t orotscrnang, rotscrnang, dead_flag;
    int16_t holoduke_on, pycount;
    int16_t transporter_hold, clipdist;

    uint8_t max_secret_rooms, secret_rooms;
    uint8_t quick_kick, last_quick_kick;
    uint8_t return_to_center, reloading, weapreccnt;
    uint8_t aim_mode, auto_aim, weaponswitch, movement_lock, team;
    uint8_t tipincs, hbomb_hold_delay, frag_ps, kickback_pic;

    uint8_t gm, on_warping_sector, footprintcount, hurt_delay;
    uint8_t hbomb_on, jumping_toggle, rapid_fire_hold, on_ground;
    uint8_t inven_icon, buttonpalette, over_shoulder_on, show_empty_weapon;

    uint8_t jetpack_on, spritebridge;
    uint8_t scuba_on, footprintpal, heat_on, invdisptime;

    uint8_t holster_weapon, falling_counter, footprintshade;
    uint8_t last_full_weapon;

    uint8_t toggle_key_flag, knuckle_incs, knee_incs, access_incs;
    uint8_t walking_snd_toggle, palookup, hard_landing, fist_incs;

    int8_t numloogs, loogcnt, scream_voice;
    int8_t last_weapon, cheat_phase, weapon_pos, wantweaponfire, curr_weapon;

    uint8_t palette;
    int8_t last_used_weapon;

    int8_t crouch_toggle;
    int8_t padding_[1];
} DukePlayer_t;

EDUKE32_STATIC_ASSERT(sizeof(DukePlayer_t) % 4 == 0);
EDUKE32_STATIC_ASSERT(sizeof(DukePlayer_t) == 640); // this needs to stay the same size for savegame compatibility

typedef struct
{
    DukePlayer_t *ps;
    input_t input;

    int horizRecenter;
    int horizAngleAdjust;
    int horizSkew;

    double lastViewUpdate;

    int32_t netsynctime;
    int32_t pcolor, pteam;
    int16_t ping;
    // NOTE: wchoice[HANDREMOTE_WEAPON .. MAX_WEAPONS-1] unused
    uint8_t frags[MAXPLAYERS], wchoice[MAX_WEAPONS];

    char smoothcamera;
    char vote, gotvote, pingcnt, playerquitflag,
        ready; // currently unused. May be used later to indicate that a player has pressed use on intermission to indicate they are ready to go on to the next map
    char user_name[32];
    uint32_t revision;
} playerdata_t;
#pragma pack(pop)

typedef struct
{
    // NOTE: the member names must be identical to aplWeapon* suffixes.
    int32_t WorksLike;  // What the original works like
    int32_t Clip;  // number of items in magazine
    int32_t Reload;  // delay to reload (include fire)
    int32_t FireDelay;  // delay to fire
    int32_t TotalTime;  // The total time the weapon is cycling before next fire.
    int32_t HoldDelay;  // delay after release fire button to fire (0 for none)
    int32_t Flags;  // Flags for weapon
    int32_t Shoots;  // what the weapon shoots
    int32_t SpawnTime;  // the frame at which to spawn an item
    int32_t Spawn;  // the item to spawn
    int32_t ShotsPerBurst;  // number of shots per 'burst' (one ammo per 'burst')
    int32_t InitialSound;  // Sound made when weapon starts firing. zero for no sound
    int32_t FireSound;  // Sound made when firing (each time for automatic)
    int32_t Sound2Time;  // Alternate sound time
    int32_t Sound2Sound;  // Alternate sound sound ID
    int32_t ReloadSound1;  // Sound of magazine being removed
    int32_t ReloadSound2;  // Sound of magazine being inserted
    int32_t SelectSound;  // Sound of weapon being selected
    int32_t FlashColor;  // Muzzle flash color
} weapondata_t;

#define PWEAPON(Player, Weapon, Wmember) (aplWeapon ## Wmember [Weapon][Player])
extern intptr_t         *aplWeaponClip[MAX_WEAPONS];            // number of items in clip
extern intptr_t         *aplWeaponReload[MAX_WEAPONS];          // delay to reload (include fire)
extern intptr_t         *aplWeaponFireDelay[MAX_WEAPONS];       // delay to fire
extern intptr_t         *aplWeaponHoldDelay[MAX_WEAPONS];       // delay after release fire button to fire (0 for none)
extern intptr_t         *aplWeaponTotalTime[MAX_WEAPONS];       // The total time the weapon is cycling before next fire.
extern intptr_t         *aplWeaponFlags[MAX_WEAPONS];           // Flags for weapon
extern intptr_t         *aplWeaponShoots[MAX_WEAPONS];          // what the weapon shoots
extern intptr_t         *aplWeaponSpawnTime[MAX_WEAPONS];       // the frame at which to spawn an item
extern intptr_t         *aplWeaponSpawn[MAX_WEAPONS];           // the item to spawn
extern intptr_t         *aplWeaponShotsPerBurst[MAX_WEAPONS];   // number of shots per 'burst' (one ammo per 'burst'
extern intptr_t         *aplWeaponWorksLike[MAX_WEAPONS];       // What original the weapon works like
extern intptr_t         *aplWeaponInitialSound[MAX_WEAPONS];    // Sound made when initialy firing. zero for no sound
extern intptr_t         *aplWeaponFireSound[MAX_WEAPONS];       // Sound made when firing (each time for automatic)
extern intptr_t         *aplWeaponSound2Time[MAX_WEAPONS];      // Alternate sound time
extern intptr_t         *aplWeaponSound2Sound[MAX_WEAPONS];     // Alternate sound sound ID
extern intptr_t         *aplWeaponReloadSound1[MAX_WEAPONS];    // Sound of magazine being removed
extern intptr_t         *aplWeaponReloadSound2[MAX_WEAPONS];    // Sound of magazine being inserted
extern intptr_t         *aplWeaponSelectSound[MAX_WEAPONS];     // Sound for weapon selection
extern intptr_t         *aplWeaponFlashColor[MAX_WEAPONS];      // Color for polymer muzzle flash

typedef struct {
    int32_t workslike, cstat; // 8b
    int32_t hitradius, range, flashcolor; // 12b
    int16_t spawns, sound, isound, vel; // 8b
    int16_t decal, trail, tnum, drop; // 8b
    int16_t offset, bounces, bsound; // 6b
    int16_t toffset; // 2b
    int16_t extra, extra_rand; // 4b
    int8_t sxrepeat, syrepeat, txrepeat, tyrepeat; // 4b
    int8_t shade, xrepeat, yrepeat, pal; // 4b
    int8_t movecnt; // 1b
    uint8_t clipdist; // 1b
    int8_t filler[2]; // 2b
    int32_t userdata; // 4b
} projectile_t;

typedef struct {
    int32_t cur, count;  // "cur" is the only member that is *used*
    int32_t gunposx, lookhalfang;  // weapon_xoffset, ps->look_ang>>1
    int32_t gunposy, lookhoriz;  // gun_pos, looking_arc
    int32_t shade;
} hudweapon_t;

extern input_t          inputfifo[MOVEFIFOSIZ][MAXPLAYERS];
extern playerspawn_t    g_playerSpawnPoints[MAXPLAYERS];
extern playerdata_t     *const g_player;
extern int16_t          WeaponPickupSprites[MAX_WEAPONS];
extern hudweapon_t      hudweap;
extern int32_t          g_levelTextTime;
extern int32_t          g_myAimMode;
extern int32_t          g_numObituaries;
extern int32_t          g_numSelfObituaries;
extern int32_t          mouseyaxismode;
extern int32_t          ticrandomseed;
extern double           g_lastInputTicks;

#define SHOOT_HARDCODED_ZVEL INT32_MIN

int A_ShootWithZvel(int spriteNum, int projecTile, int forceZvel);
static inline int A_Shoot(int spriteNum, int projecTile)
{
    return A_ShootWithZvel(spriteNum, projecTile, SHOOT_HARDCODED_ZVEL);
}

static inline void P_PalFrom(DukePlayer_t *pPlayer, uint8_t f, uint8_t r, uint8_t g, uint8_t b)
{
    pPlayer->pals.f = f;
    pPlayer->pals.r = r;
    pPlayer->pals.g = g;
    pPlayer->pals.b = b;
}

void    P_AddKills(DukePlayer_t * pPlayer, uint16_t kills);
int32_t A_GetHitscanRange(int spriteNum);
void    P_GetInput(int playerNum);
void P_UpdateAngles(int const playerNum, input_t &input);
void P_AddAmmo(DukePlayer_t * pPlayer, int weaponNum, int addAmount);
void    P_AddWeapon(DukePlayer_t *pPlayer, int weaponNum, int switchWeapon);
void    P_CheckWeapon(DukePlayer_t *pPlayer);
void    P_DisplayScuba(void);
void    P_DisplayWeapon(void);
void P_DropWeapon(int playerNum);
int     P_FindOtherPlayer(int playerNum, int32_t *pDist);
void    P_FragPlayer(int playerNum);
void    P_UpdatePosWhenViewingCam(DukePlayer_t *pPlayer);
void    P_ProcessInput(int playerNum);
void    P_QuickKill(DukePlayer_t *pPlayer);
void    P_SelectNextInvItem(DukePlayer_t *pPlayer);
void    P_UpdateScreenPal(DukePlayer_t *pPlayer);
void    P_EndLevel(void);
int     P_GetHudPal(const DukePlayer_t *pPlayer);
int     P_GetKneePal(const DukePlayer_t *pPlayer);
#ifdef __cplusplus
}
int     P_GetKneePal(const DukePlayer_t *pPlayer, int hudPal);
extern "C" {
#endif
int     P_GetOverheadPal(const DukePlayer_t *pPlayer);

int Proj_GetDamage(projectile_t const *pProj);

void P_SetWeaponGamevars(int playerNum, const DukePlayer_t *pPlayer);

// Get the player index given an APLAYER sprite pointer.
static inline int P_GetP(const void *pSprite)
{
#if 0  // unprotected player index retrieval
    return spr->yvel;
#elif defined NETCODE_DISABLE
    UNREFERENCED_PARAMETER(pSprite);  // for NDEBUG build
    // NOTE: In the no-netcode build, there's no point to pass player indices
    // at all since there is ever only one player. However, merely returning 0
    // would mean making this build less strict than the normal one.
    Bassert(((uspriteptr_t)pSprite)->yvel == 0);
    return 0;
#else
    int playerNum = ((uspriteptr_t)pSprite)->yvel;
    // [JM] Check against MAXPLAYERS as opposed to g_mostConcurrentPlayers
    //      to prevent CON for disconnected/fake players from executing as playernum 0.
    if ((unsigned)playerNum >= MAXPLAYERS)
        playerNum = 0;
    return playerNum;
#endif
}

// Get the player index given an APLAYER sprite index.
static inline int P_Get(int32_t spriteNum) { return P_GetP((uspriteptr_t)&sprite[spriteNum]); }

#ifdef __cplusplus
}
#endif

extern int portableBackupSave(const char *, const char *, int, int);

#endif

repo_name: orochi/nblood

license: mit


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 "anim.h"
#include "cmdline.h"
#include "demo.h"
#include "duke3d.h"
#include "menus.h"
#include "savegame.h"
#include "sbar.h"

#include "vfs.h"

static uint8_t precachehightile[2][(MAXTILES+7)>>3];
static int32_t g_precacheCount;

static int32_t NET_75_CHECK = 0;

static void flag_precache(int32_t tile, int32_t type)
{
    if (!bitmap_test(gotpic, tile))
        g_precacheCount++;

    bitmap_set(gotpic, tile);
    bitmap_set(precachehightile[type], tile);
}

static void tloadtile(int tilenume, int type)
{
    int firstTile, lastTile;

    if ((picanm[tilenume].sf & PICANM_ANIMTYPE_MASK) == PICANM_ANIMTYPE_BACK)
    {
        firstTile = tilenume - picanm[tilenume].num;
        lastTile  = tilenume;
    }
    else
    {
        firstTile = tilenume;
        lastTile  = tilenume + picanm[tilenume].num;
    }

    for (; firstTile <= lastTile; firstTile++)
        flag_precache(firstTile, type);
}

static void cacheTilesForSprite(int spriteNum)
{
    if (ud.monsters_off && A_CheckEnemySprite(&sprite[spriteNum])) return;

    int const picnum = sprite[spriteNum].picnum == RESPAWN ? sprite[spriteNum].hitag : sprite[spriteNum].picnum;
    int extraTiles = 1;

    for (int j = picnum; j <= g_tile[picnum].cacherange; j++)
        tloadtile(j, 1);

#ifndef EDUKE32_STANDALONE
    switch (tileGetMapping(picnum))
    {
    case HYDRENT__:
        tloadtile(BROKEFIREHYDRENT,1);
        for (int j = TOILETWATER; j < (TOILETWATER+4); j++) tloadtile(j,1);
        break;
    case TOILET__:
        tloadtile(TOILETBROKE,1);
        for (int j = TOILETWATER; j < (TOILETWATER+4); j++) tloadtile(j,1);
        break;
    case STALL__:
        tloadtile(STALLBROKE,1);
        for (int j = TOILETWATER; j < (TOILETWATER+4); j++) tloadtile(j,1);
        break;
    case RUBBERCAN__:
        extraTiles = 2;
        break;
    case TOILETWATER__:
        extraTiles = 4;
        break;
    case FEMPIC1__:
        extraTiles = 44;
        break;
    case LIZTROOP__:
    case LIZTROOPRUNNING__:
    case LIZTROOPSHOOT__:
    case LIZTROOPJETPACK__:
    case LIZTROOPONTOILET__:
    case LIZTROOPDUCKING__:
        for (int j = LIZTROOP; j < (LIZTROOP+72); j++) tloadtile(j,1);
        for (int j=HEADJIB1; j<LEGJIB1+3; j++) tloadtile(j,1);
        extraTiles = 0;
        break;
    case WOODENHORSE__:
        extraTiles = 5;
        for (int j = HORSEONSIDE; j < (HORSEONSIDE+4); j++) tloadtile(j,1);
        break;
    case NEWBEAST__:
    case NEWBEASTSTAYPUT__:
        extraTiles = 90;
        break;
    case BOSS1__:
    case BOSS2__:
    case BOSS3__:
    case SHARK__:
        extraTiles = 30;
        break;
    case OCTABRAIN__:
    case OCTABRAINSTAYPUT__:
    case COMMANDER__:
    case COMMANDERSTAYPUT__:
        extraTiles = 38;
        break;
    case RECON__:
        extraTiles = 13;
        break;
    case PIGCOP__:
    case PIGCOPDIVE__:
        extraTiles = 61;
        break;
    case LIZMAN__:
    case LIZMANSPITTING__:
    case LIZMANFEEDING__:
    case LIZMANJUMP__:
        for (int j=LIZMANHEAD1; j<LIZMANLEG1+3; j++) tloadtile(j,1);
        extraTiles = 80;
        break;
    case APLAYER__:
        extraTiles = 0;
        if ((g_netServer || ud.multimode > 1))
        {
            extraTiles = 5;
            for (int j = 1420; j < 1420+106; j++) tloadtile(j,1);
        }
        break;
    case ATOMICHEALTH__:
        extraTiles = 14;
        break;
    case DRONE__:
        extraTiles = 10;
        break;
    case EXPLODINGBARREL__:
    case SEENINE__:
    case OOZFILTER__:
        extraTiles = 3;
        break;
    case NUKEBARREL__:
    case CAMERA1__:
        extraTiles = 5;
        break;
        // caching of HUD sprites for weapons that may be in the level
    case CHAINGUNSPRITE__:
        for (int j=CHAINGUN; j<=CHAINGUN+7; j++) tloadtile(j,1);
        break;
    case RPGSPRITE__:
        tloadtile(WT_WIDE(RPGGUN), 1);
        for (int j=RPGGUN+1; j<=RPGGUN+2; j++) tloadtile(j,1);
        break;
    case FREEZESPRITE__:
        tloadtile(WT_WIDE(FREEZE), 1);
        tloadtile(WORLDTOUR ? FREEZEFIREWIDE : FREEZE+2, 1);
        for (int j=FREEZE+3; j<=FREEZE+5; j++) tloadtile(j,1);
        break;
    case GROWSPRITEICON__:
    case SHRINKERSPRITE__:
    {
        int32_t const tile = WT_WIDE(SHRINKER);
        for (int j=tile-2; j<=tile+1; j++) tloadtile(j,1);
        for (int j=SHRINKER+2; j<=SHRINKER+5; j++) tloadtile(j,1);
        break;
    }
    case HBOMBAMMO__:
    case HEAVYHBOMB__:
        for (int j=HANDREMOTE; j<=HANDREMOTE+5; j++) tloadtile(j,1);
        break;
    case TRIPBOMBSPRITE__:
        for (int j=HANDHOLDINGLASER; j<=HANDHOLDINGLASER+4; j++) tloadtile(j,1);
        break;
    case SHOTGUNSPRITE__:
        tloadtile(SHOTGUNSHELL,1);
        for (int j=SHOTGUN; j<=SHOTGUN+6; j++) tloadtile(j,1);
        break;
    case DEVISTATORSPRITE__:
        for (int j=DEVISTATOR; j<=DEVISTATOR+1; j++) tloadtile(j,1);
        break;
    }
#endif

    for (int j = picnum; j < (picnum + extraTiles); j++)
        tloadtile(j, 1);
}

#ifndef EDUKE32_STANDALONE
static void cacheDukeTiles(void)
{
    tloadtile(sbartile(), 1);

    if ((g_netServer || ud.multimode > 1))
        tloadtile(FRAGBAR, 1);

    tloadtile(VIEWSCREEN, 1);

    for (int i = STARTALPHANUM; i < ENDALPHANUM+1; i++)
        tloadtile(i, 1);
    for (int i = BIGALPHANUM-11; i < BIGALPHANUM+82; i++)
        tloadtile(i, 1);
    for (int i = MINIFONT; i < MINIFONT+93; i++)
        tloadtile(i, 1);

    for (int i = FOOTPRINTS; i < FOOTPRINTS+3; i++)
        tloadtile(i, 1);

    for (int i = BURNING; i < BURNING+14; i++)
        tloadtile(i, 1);
    for (int i = BURNING2; i < BURNING2+14; i++)
        tloadtile(i, 1);

    for (int i = CRACKKNUCKLES; i < CRACKKNUCKLES+4; i++)
        tloadtile(i, 1);

    for (int i = FIRSTGUN; i < FIRSTGUN+3; i++)
        tloadtile(i, 1);
    tloadtile(FIRSTGUNRELOAD, 1);
    tloadtile(WORLDTOUR ? FIRSTGUNRELOADWIDE : FIRSTGUNRELOAD+1, 1);
    tloadtile(FIRSTGUNRELOAD+2, 1);
    tloadtile(FIRSTGUNRELOAD+4, 1);
    for (int i = SHELL; i < SHELL+2; i++)
        tloadtile(i, 1);

    for (int i = EXPLOSION2; i < EXPLOSION2+21; i++)
        tloadtile(i, 1);

    for (int i = COOLEXPLOSION1; i < COOLEXPLOSION1+21; i++)
        tloadtile(i, 1);

    tloadtile(BULLETHOLE, 1);
    tloadtile(BLOODPOOL, 1);

    for (int i = TRANSPORTERBEAM; i < (TRANSPORTERBEAM+6); i++)
        tloadtile(i, 1);

    for (int i = SMALLSMOKE; i < (SMALLSMOKE+4); i++)
        tloadtile(i, 1);
    for (int i = SHOTSPARK1; i < (SHOTSPARK1+4); i++)
        tloadtile(i, 1);

    for (int i = BLOOD; i < (BLOOD+4); i++)
        tloadtile(i, 1);
    for (int i = JIBS1; i < (JIBS5+5); i++)
        tloadtile(i, 1);
    for (int i = JIBS6; i < (JIBS6+8); i++)
        tloadtile(i, 1);

    for (int i = SCRAP1; i < (SCRAP1+29); i++)
        tloadtile(i, 1);

    tloadtile(FIRELASER, 1);

    for (int i = TRANSPORTERSTAR; i < TRANSPORTERSTAR+6; i++)
        tloadtile(i, 1);

    for (int i = FORCERIPPLE; i < (FORCERIPPLE+9); i++)
        tloadtile(i, 1);

    for (int i = MENUSCREEN; i < DUKECAR; i++)
        tloadtile(i, 1);

    for (int i = RPG; i < RPG+7; i++)
        tloadtile(i, 1);
    for (int i = FREEZEBLAST; i < FREEZEBLAST+3; i++)
        tloadtile(i, 1);
    for (int i = SHRINKSPARK; i < SHRINKSPARK+4; i++)
        tloadtile(i, 1);
    for (int i = GROWSPARK; i < GROWSPARK+4; i++)
        tloadtile(i, 1);
    for (int i = SHRINKEREXPLOSION; i < SHRINKEREXPLOSION+4; i++)
        tloadtile(i, 1);
    for (int i = MORTER; i < MORTER+4; i++)
        tloadtile(i, 1);
    for (int i = 0; i <= 60; i++)
        tloadtile(i, 1);
}
#endif

static void cacheFlaggedTiles(void)
{
    for (int i = 0; i < MAXTILES; i++)
    {
        if (g_tile[i].flags & SFLAG_PROJECTILE)
            tloadtile(i, 1);

        if (A_CheckSpriteTileFlags(i, SFLAG_CACHE))
            for (int j = i; j <= g_tile[i].cacherange; j++)
                tloadtile(j, 1);
    }

#ifndef EDUKE32_STANDALONE
    cacheDukeTiles();
#endif
}

static void G_DemoLoadScreen(const char *statustext, int const loadScreenTile, int percent)
{
    if (statustext == NULL)
    {
        videoClearScreen(0L);
        // g_player[myconnectindex].ps->palette = palette;
        // G_FadePalette(0,0,0,0);
        P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 0);  // JBF 20040308
    }

    if ((unsigned)loadScreenTile < (MAXTILES<<1))
    {
        rotatesprite_fs(320<<15, 200<<15, 65536L, 0, loadScreenTile, 0, 0, 2+8+64+BGSTRETCH);
    }
    else
    {
        videoNextPage();
        return;
    }

    menutext_center(105, "Loading...");

    if (statustext)
        gametext_center_number(180, statustext);

    VM_OnEventWithReturn(EVENT_DISPLAYLOADINGSCREEN, g_player[screenpeek].ps->i, screenpeek, percent);
    videoNextPage();
}

static void G_DoLoadScreen(const char *statustext, int percent)
{
    int const loadScreenTile = VM_OnEventWithReturn(EVENT_GETLOADTILE, g_player[screenpeek].ps->i, screenpeek, LOADSCREEN);

    if (ud.recstat == 2)
    {
        G_DemoLoadScreen(statustext, loadScreenTile, percent);
        return;
    }

    int const screenSize = ud.screen_size;

    P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 1);

    if (statustext == NULL)
    {
        ud.screen_size = 0;
        G_UpdateScreenArea();
        videoClearScreen(0L);
    }

    if ((unsigned)loadScreenTile < (MAXTILES<<1))
    {
        videoClearScreen(0);
        rotatesprite_fs(320<<15, 200<<15, 65536L, 0, loadScreenTile, 0, 0, 2+8+64+BGSTRETCH);
    }
    else
    {
        videoNextPage();
        return;
    }

    if (boardfilename[0] != 0 && ud.level_number == 7 && ud.volume_number == 0)
    {
        menutext_center(90, "Loading User Map");
        gametext_center_shade_pal(90+10, boardfilename, 14, 2);
    }
    else
    {
        menutext_center(90, "Loading");

        if (g_mapInfo[(ud.volume_number*MAXLEVELS) + ud.level_number].name != NULL)
            menutext_center(90+16+8, g_mapInfo[(ud.volume_number*MAXLEVELS) + ud.level_number].name);
    }

#ifndef EDUKE32_TOUCH_DEVICES
    if (statustext)
        gametext_center_number(180, statustext);
#endif

    if (percent != -1)
    {
        int const     width = scale(scale(xdim-1, 288, 320), percent, 100);
        int constexpr tile  = 929;
        int constexpr bits  = 2+8+16;

        rotatesprite(31<<16 , 145<<16, 65536, 0, tile, 15, 0, bits, 0, 0, width, ydim-1);
        rotatesprite(159<<16, 145<<16, 65536, 0, tile, 15, 0, bits, 0, 0, width, ydim-1);

        rotatesprite(30<<16 , 144<<16, 65536, 0, tile, 0, 0, bits, 0, 0, width, ydim-1);
        rotatesprite(158<<16, 144<<16, 65536, 0, tile, 0, 0, bits, 0, 0, width, ydim-1);
    }

    VM_OnEventWithReturn(EVENT_DISPLAYLOADINGSCREEN, g_player[screenpeek].ps->i, screenpeek, percent);
    videoNextPage();

    if (!statustext)
    {
        KB_FlushKeyboardQueue();
        ud.screen_size = screenSize;
    }
}

static void cacheExtraTextureMaps(int tileNum, int type)
{
#ifdef USE_OPENGL
    for (int i = 0; i < MAXPALOOKUPS-RESERVEDPALS-1; i++)
    {
#ifdef POLYMER
        if (videoGetRenderMode() != REND_POLYMER || !polymer_havehighpalookup(0, i))
#endif
            polymost_precache(tileNum, i, type);
    }
#else
    UNREFERENCED_PARAMETER(tileNum);
    UNREFERENCED_PARAMETER(type);
#endif

#ifdef USE_GLEXT
    if (r_detailmapping)
        polymost_precache(tileNum, DETAILPAL, type);

    if (r_glowmapping)
        polymost_precache(tileNum, GLOWPAL, type);
#endif
#ifdef POLYMER
    if (videoGetRenderMode() == REND_POLYMER)
    {
        if (pr_specularmapping)
            polymost_precache(tileNum, SPECULARPAL, type);

        if (pr_normalmapping)
            polymost_precache(tileNum, NORMALPAL, type);
    }
#endif
}

void G_CacheMapData(void)
{
    if (ud.recstat == 2 || !ud.config.useprecache)
        return;

    g_precacheCount = 0;
    Bmemset(gotpic, 0, sizeof(gotpic));
    Bmemset(precachehightile, 0, sizeof(precachehightile));

    S_TryPlaySpecialMusic(MUS_LOADING);

#if defined EDUKE32_TOUCH_DEVICES && defined USE_OPENGL
    polymost_glreset();
#endif

    uint32_t const cacheStartTime = timerGetTicks();

    cacheFlaggedTiles();

    for (int i=0; i<numwalls; i++)
    {
        tloadtile(wall[i].picnum, 0);

        if (wall[i].overpicnum >= 0)
            tloadtile(wall[i].overpicnum, 0);
    }

    for (int i=0; i<numsectors; i++)
    {
        tloadtile(sector[i].floorpicnum, 0);
        tloadtile(sector[i].ceilingpicnum, 0);

        for (int SPRITES_OF_SECT(i, j))
        {
            if (sprite[j].xrepeat != 0 && sprite[j].yrepeat != 0 && (sprite[j].cstat & CSTAT_SPRITE_INVISIBLE) == 0)
                cacheTilesForSprite(j);
        }
    }

    int cnt = 0;
    int cntDisplayed = -1;
    int pctDisplayed = -1;
    int i = 0;

    while (cnt < g_precacheCount)
    {
        if (bitmap_test(gotpic, i))
        {
            cnt++;

            if (waloff[i] == 0)
                tileLoad((int16_t)i);

            for (int j = 0; j < 2; j++)
            {
                if (bitmap_test(precachehightile[j], i))
                {
                    tileLoadScaled(i);

                    if (videoGetRenderMode() != REND_CLASSIC)
                        cacheExtraTextureMaps(i, j);
                }
            }

            gameHandleEvents();
            if (KB_KeyPressed(sc_Space))
                break;
        }
        i++;

        if (cntDisplayed+(i&7) < cnt-(i&7) && engineFPSLimit(true))
        {
            int const percentComplete = min(100, tabledivide32(100 * cntDisplayed, g_precacheCount));
            cntDisplayed = logapproach(cntDisplayed, cnt);
            pctDisplayed = logapproach(pctDisplayed, percentComplete);
            Bsprintf(tempbuf, "Loaded %d%% (%d/%d textures)\n", pctDisplayed, cntDisplayed, g_precacheCount);
            G_DoLoadScreen(tempbuf, pctDisplayed);
        }
    }

    Bmemset(gotpic, 0, sizeof(gotpic));

    LOG_F(INFO, "Cache time: %dms.", timerGetTicks() - cacheStartTime);
}

int fragbarheight(void)
{
    if (ud.screen_size > 0 && !(ud.statusbarflags & STATUSBAR_NOFRAGBAR)
#ifdef SPLITSCREEN_MOD_HACKS
        && !g_fakeMultiMode
#endif
        && (g_netServer || ud.multimode > 1) && GTFLAGS(GAMETYPE_FRAGBAR))
    {
        int j = 0;

        for (int TRAVERSE_CONNECT(i))
            if (i > j)
                j = i;

        return ((j + 3) >> 2) * tilesiz[FRAGBAR].y;
    }

    return 0;
}

void G_UpdateScreenArea(void)
{
    if (!in3dmode())
        return;

    if ((ud.screen_size = clamp(ud.screen_size, 0, 64)) == 0)
        renderFlushPerms();

    int const screenSize    = max(ud.screen_size - 8, 0);
    int const bottomStatusY = tilesiz[sbartile()].y;

    vec2_t v1 = { scale(screenSize, xdim, 160),
                  scale(screenSize, (200 * 100) - (bottomStatusY * ud.statusbarscale), 200 - bottomStatusY) };
    vec2_t v2 = { xdim - v1.x, 200 * 100 - v1.y };

    v1.y += fragbarheight() * 100;

    if (ud.screen_size >= 8 && ud.statusbarmode == 0)
        v2.y -= bottomStatusY * ud.statusbarscale;

    v1.y = scale(v1.y, ydim, 200 * 100);
    v2.y = scale(v2.y, ydim, 200 * 100);

    if (VM_HaveEvent(EVENT_UPDATESCREENAREA))
    {
        ud.returnvar[0] = v1.y;
        ud.returnvar[1] = v2.x;
        ud.returnvar[2] = v2.y;
        v1.x = VM_OnEventWithReturn(EVENT_UPDATESCREENAREA, g_player[screenpeek].ps->i, screenpeek, v1.x);
        v1.y = ud.returnvar[0];
        v2.x = ud.returnvar[1];
        v2.y = ud.returnvar[2];
    }

    videoSetViewableArea(v1.x, v1.y, v2.x-1, v2.y-1);

    G_GetCrosshairColor();
    G_SetCrosshairColor(CrosshairColors.r, CrosshairColors.g, CrosshairColors.b);

    pub = NUMPAGES;
    pus = NUMPAGES;
}

void P_MoveToRandomSpawnPoint(int playerNum)
{
    auto &p = *g_player[playerNum].ps;
    int i = playerNum;

    if ((g_netServer || ud.multimode > 1) && !(g_gametypeFlags[ud.coop] & GAMETYPE_FIXEDRESPAWN))
    {
        i = krand() % g_playerSpawnCnt;

        if (g_gametypeFlags[ud.coop] & GAMETYPE_TDMSPAWN)
        {
            uint32_t pdist = INT_MAX;

            for (int TRAVERSE_CONNECT(j))
            {
                if (j == playerNum)
                    continue;

                auto const &op = *g_player[j].ps;

                // pick a spawn near a living teammate
                if (op.team == p.team && sprite[op.i].extra > 0)
                {
                    for (int k = 0; k < g_playerSpawnCnt; k++)
                    {
                        uint32_t const dist = FindDistance2D(op.pos.x - g_playerSpawnPoints[k].x,
                                                             op.pos.y - g_playerSpawnPoints[k].y);
                        if (dist < pdist)
                            i = k, pdist = dist;
                    }
                    break;
                }
            }
        }
    }

    p.opos = p.pos = g_playerSpawnPoints[i].xyz;

    p.bobpos     = p.pos.xy;
    p.cursectnum = g_playerSpawnPoints[i].sect;
    p.q16ang     = fix16_from_int(g_playerSpawnPoints[i].ang);

    sprite[p.i].cstat = CSTAT_SPRITE_BLOCK + CSTAT_SPRITE_BLOCK_HITSCAN;
}

static inline void P_ResetTintFade(DukePlayer_t *const pPlayer)
{
    pPlayer->pals.f = 0;
}

void P_ResetOffsets(DukePlayer_t *const pPlayer)
{
    pPlayer->floorzoffset  = 40 << 8;
    pPlayer->waterzoffset  = 34 << 8;
    pPlayer->minwaterzdist = 16 << 8;
    pPlayer->shrunkzoffset = 12 << 8;
    pPlayer->spritezoffset = 38 << 8 /*PHEIGHT*/;

    pPlayer->gravity = g_spriteGravity + 80;

    pPlayer->crouchzincrement    = 2048 + 768;
    pPlayer->crouchspeedmodifier = 8192;

    pPlayer->maxswimzvel       = 1536;
    pPlayer->minswimzvel       = 256;
    pPlayer->swimspeedmodifier = 5120;
    pPlayer->swimzincrement    = 348;

    pPlayer->jetpackzincrement = 2048;
}

void P_ResetExtents(DukePlayer_t* const pPlayer)
{
    pPlayer->autostep = 20 << 8;
    pPlayer->autostep_sbw = 4 << 8;
    pPlayer->clipdist = 164;
    pPlayer->runspeed = g_playerFriction;

    P_ResetOffsets(pPlayer);
}

void P_ResetMultiPlayer(int playerNum)
{
    auto &p = *g_player[playerNum].ps;

    Bassert((unsigned)p.i < MAXSPRITES);

    auto &s = sprite[p.i];
    auto &a = actor[p.i];

    vec3_t tmpvect = p.pos;

    tmpvect.z += p.spritezoffset;

    P_MoveToRandomSpawnPoint(playerNum);

    a.bpos = p.opos = p.pos;
    p.bobpos = p.pos.xy;

    s.xyz = p.pos;

    setsprite(p.i, &tmpvect);

    s.clipdist = 64;
    s.cstat    = 257;
    s.owner    = p.i;
    s.pal      = p.palookup;
    s.shade    = -12; // ???
    s.xoffset  = 0;
    s.xrepeat  = 42;
    s.yrepeat  = 36;

    p.last_extra = s.extra = p.max_player_health;
    p.inv_amount[GET_SHIELD] = g_startArmorAmount;

    p.dead_flag       = 0;
    p.falling_counter = 0;
    p.footprintcount  = 0;
    p.frag_ps         = playerNum;
    p.fta             = 0;
    p.ftq             = 0;
    p.on_crane        = -1;
    p.opyoff          = 0;
    p.q16horiz        = F16(100);
    p.q16horizoff     = 0;
    p.rotscrnang      = 0;
    p.vel             = { 0, 0, 0 };
    p.wackedbyactor   = -1;
    p.wantweaponfire  = -1;
    p.weapreccnt      = 0;

    P_ResetTintFade(&p);

    a.cgg       = 0;
    a.dispicnum = 0;
    a.htextra   = -1;
    a.movflag   = 0;
    a.htowner   = p.i;
    a.stayput   = -1;
    a.t_data[4] = 0;
    a.tempang   = 0;

    P_ResetInventory(playerNum);
    P_ResetWeapons(playerNum);

    p.reloading     = 0;
    p.movement_lock = 0;

    VM_OnEvent(EVENT_RESETPLAYER, p.i, playerNum);
}

void P_ResetPlayer(int playerNum)
{
    auto &thisPlayer = g_player[playerNum];
    auto &p          = *thisPlayer.ps;

    ud.show_help  = 0;
    ud.showallmap = 0;

    p.access_spritenum   = -1;
    p.actorsqu           = -1;
    p.airleft            = 15 * GAMETICSPERSEC;
    p.bobcounter         = 0;
    p.buttonpalette      = 0;
    p.cheat_phase        = 0;
    p.crack_time         = 0;
    p.dead_flag          = 0;
    p.dummyplayersprite  = -1;
    p.extra_extra8       = 0;
    p.falling_counter    = 0;
    p.fist_incs          = 0;
    p.footprintcount     = 0;
    p.footprintpal       = 0;
    p.footprintshade     = 0;
    p.frag               = 0;
    p.frag_ps            = playerNum;
    p.fraggedself        = 0;
    p.fric               = { 0, 0 };
    p.fta                = 0;
    p.ftq                = 0;
    p.got_access         = ((g_netServer || ud.multimode > 1) && (g_gametypeFlags[ud.coop] & GAMETYPE_ACCESSATSTART)) ? 7 : 0;
    p.hard_landing       = 0;
    p.hbomb_hold_delay   = 0;
    p.heat_on            = 0;
    p.holoduke_on        = -1;
    p.holster_weapon     = 0;
    p.hurt_delay         = 0;
    p.invdisptime        = 0;
    p.jetpack_on         = 0;
    p.jumping_counter    = 0;
    p.jumping_toggle     = 0;
    p.knee_incs          = 0;
    p.knuckle_incs       = 1;
    p.last_full_weapon   = 0;
    p.last_pissed_time   = 0;
    p.loogcnt            = 0;
    p.look_ang           = 512 - ((ud.level_number&1)<<10);
    p.movement_lock      = 0;
    p.newowner           = -1;
    p.on_crane           = -1;
    p.on_ground          = 0;
    p.on_warping_sector  = 0;
    p.one_eighty_count   = 0;
    p.opyoff             = 0;
    p.oq16horiz          = F16(140);
    p.orotscrnang        = 1;  // JBF 20031220
    p.over_shoulder_on   = 0;
    p.palette            = BASEPAL;
    p.player_par         = 0;
    p.pycount            = 0;
    p.pyoff              = 0;
    p.q16angvel          = 0;
    p.q16horiz           = F16(140);
    p.q16horizoff        = 0;
    p.quick_kick         = 0;
    p.random_club_frame  = 0;
    p.rapid_fire_hold    = 0;
    p.reloading          = 0;
    p.return_to_center   = 9;
    p.rotscrnang         = 0;
    p.sbs                = 0;
    p.show_empty_weapon  = 0;
    p.somethingonplayer  = -1;
    p.spritebridge       = 0;
    p.subweapon          = 0;
    p.tipincs            = 0;
    p.toggle_key_flag    = 0;
    p.transporter_hold   = 0;
    p.vel.x              = 0;
    p.vel.y              = 0;
    p.vel.z              = 0;
    p.wackedbyactor      = -1;
    p.walking_snd_toggle = 0;
    p.wantweaponfire     = -1;
    p.weapon_ang         = 0;
    p.weapon_pos         = WEAPON_POS_START;
    p.weapon_sway        = 0;

    pus = 1;

    if (p.inv_amount[GET_STEROIDS] < 400)
    {
        p.inv_amount[GET_STEROIDS] = 0;
        p.inven_icon = ICON_NONE;
    }

    p.kickback_pic = ((PWEAPON(playerNum, p.curr_weapon, WorksLike) == PISTOL_WEAPON)
                      && (PWEAPON(playerNum, p.curr_weapon, Reload) > PWEAPON(playerNum, p.curr_weapon, TotalTime)))
                     ? PWEAPON(playerNum, p.curr_weapon, TotalTime)
                     : 0;

    thisPlayer.smoothcamera     = false;
    thisPlayer.horizRecenter    = false;
    thisPlayer.horizSkew        = 0;
    thisPlayer.horizAngleAdjust = 0;

    P_ResetExtents(&p);
    P_UpdateScreenPal(&p);
    VM_OnEvent(EVENT_RESETPLAYER, p.i, playerNum);
}

void P_ResetWeapons(int playerNum)
{
    auto &p = *g_player[playerNum].ps;

    for (short & ammo : p.ammo_amount)
        ammo = 0;

    p.curr_weapon       = PISTOL_WEAPON;
    p.gotweapon         = ((1 << PISTOL_WEAPON) | (1 << KNEE_WEAPON) | (1 << HANDREMOTE_WEAPON));
    p.holster_weapon    = 0;
    p.kickback_pic      = PWEAPON(playerNum, p.curr_weapon, TotalTime);
    p.last_pissed_time  = 0;
    p.last_used_weapon  = -1;
    p.last_weapon       = -1;
    p.show_empty_weapon = 0;
    p.weapon_pos        = WEAPON_POS_START;

    p.ammo_amount[PISTOL_WEAPON] = min<int16_t>(p.max_ammo_amount[PISTOL_WEAPON], 48);

    VM_OnEvent(EVENT_RESETWEAPONS, p.i, playerNum);
}

void P_ResetInventory(int playerNum)
{
    auto &p = *g_player[playerNum].ps;

    Bmemset(p.inv_amount, 0, sizeof(p.inv_amount));

    p.heat_on     = 0;
    p.holoduke_on = -1;
    p.inven_icon  = ICON_NONE;
    p.jetpack_on  = 0;
    p.scuba_on    = 0;

    p.inv_amount[GET_SHIELD] = g_startArmorAmount;

    VM_OnEvent(EVENT_RESETINVENTORY, p.i, playerNum);
}

static void P_PrepForNewLevel(int playerNum, int gameMode)
{
    auto &p = *g_player[playerNum].ps;

    g_spriteDeleteQueuePos = 0;

    for (short &i : SpriteDeletionQueue)
        i = -1;

    g_animWallCnt      = 0;
    g_animateCnt       = 0;
    g_cyclerCnt        = 0;
    g_earthquakeTime   = 0;
    g_interpolationCnt = 0;

    for (int vscrIndex = 0; vscrIndex < MAX_ACTIVE_VIEWSCREENS; vscrIndex++)
    {
        g_activeVscrSprite[vscrIndex] = -1;
        g_activeVscrTile[vscrIndex] = -1;
    }

    randomseed  = 1996;
    screenpeek  = myconnectindex;
    tempwallptr = 0;

    p.actors_killed     = 0;
    p.cheat_phase       = 0;
    p.customexitsound   = 0;
    p.hbomb_on          = 0;
    p.holster_weapon    = 0;
    p.interface_toggle  = 0;
    p.last_pissed_time  = 0;
    p.last_weapon       = -1;
    p.max_actors_killed = 0;
    p.max_secret_rooms  = 0;
    p.parallax_sectnum  = -1;
    p.secret_rooms      = 0;
    p.show_empty_weapon = 0;
    p.timebeforeexit    = 0;
    p.toggle_key_flag   = 0;
    p.visibility        = ud.const_visibility;
    p.weapon_pos        = WEAPON_POS_START;
    p.weapreccnt        = 0;

    p.kickback_pic = ((PWEAPON(playerNum, p.curr_weapon, WorksLike) == PISTOL_WEAPON)
                      && (PWEAPON(playerNum, p.curr_weapon, Reload) > PWEAPON(playerNum, p.curr_weapon, TotalTime)))
                     ? PWEAPON(playerNum, p.curr_weapon, TotalTime)
                     : 0;

    ud.camerasprite = -1;
    ud.eog          = 0;
    ud.pause_on     = 0;

    if (((gameMode & MODE_EOL) != MODE_EOL && numplayers < 2 && !g_netServer)
        || (!(g_gametypeFlags[ud.coop] & GAMETYPE_PRESERVEINVENTORYDEATH) && numplayers > 1))
    {
        P_ResetWeapons(playerNum);
        P_ResetInventory(playerNum);
    }
    else if (PWEAPON(playerNum, p.curr_weapon, WorksLike) == HANDREMOTE_WEAPON)
    {
        p.ammo_amount[HANDBOMB_WEAPON]++;
        p.curr_weapon = HANDBOMB_WEAPON;
    }

    P_ResetTintFade(&p);
}

// Tweak sprites contained in moving sectors with these SE lotags.
#define FIXSPR_SELOTAGP(k) ((k) == SE_0_ROTATING_SECTOR || (k) == SE_6_SUBWAY || (k) == SE_14_SUBWAY_CAR)

// Set up sprites in moving sectors that are to be fixed wrt a certain pivot
// position and should not diverge from it due to roundoff error in the future.
// Has to be after the spawning stuff.
static void G_SetupRotfixedSprites(void)
{
    int spriteNum, nextSpriteNum;

    for (SPRITES_OF_STAT_SAFE(STAT_EFFECTOR, spriteNum, nextSpriteNum))
    {
        auto const &s = sprite[spriteNum];

        if (FIXSPR_SELOTAGP(s.lotag))
        {
#ifdef YAX_ENABLE
            int firstrun = 1;
#endif
            int sectSprite = headspritesect[s.sectnum];

            do
            {
                auto const &ss = sprite[sectSprite];
                auto       &a  = actor[sectSprite];

                // TRIPBOMB uses t_data[7] for its own purposes. Wouldn't be
                // too useful with moving sectors anyway
                if ((ROTFIXSPR_STATNUMP(ss.statnum) && ss.picnum != TRIPBOMB)
                    || ((ss.statnum == STAT_ACTOR || ss.statnum == STAT_ZOMBIEACTOR) && A_CheckSpriteFlags(sectSprite, SFLAG_ROTFIXED)))
                {
                    int const pivotSprite = (s.lotag == 0) ? s.owner : spriteNum;

                    if (sectSprite != spriteNum && sectSprite != pivotSprite && pivotSprite >= 0 && pivotSprite < MAXSPRITES)
                    {
                        // let's hope we don't step on anyone's toes here
                        a.t_data[7] = ROTFIXSPR_MAGIC | pivotSprite;  // 'rs' magic + pivot SE sprite index
                        a.t_data[8] = ss.x - sprite[pivotSprite].x;
                        a.t_data[9] = ss.y - sprite[pivotSprite].y;
                    }
                }

                sectSprite = nextspritesect[sectSprite];
#ifdef YAX_ENABLE
                if ((sectSprite < 0 && firstrun) && (s.lotag == SE_6_SUBWAY || s.lotag == SE_14_SUBWAY_CAR))
                {
                    firstrun   = 0;
                    sectSprite = actor[spriteNum].t_data[9];

                    if (sectSprite >= 0)
                        sectSprite = headspritesect[sectSprite];
                }
#endif
            }
            while (sectSprite >= 0);
        }
    }
}

static void G_SetupLightSwitches()
{
    auto tagbitmap = (uint8_t *)Xcalloc(65536 >> 3, 1);

    for (int nextSprite, SPRITES_OF_STAT_SAFE(STAT_DEFAULT, spriteNum, nextSprite))
    {
        auto &s = sprite[spriteNum];

        if (s.picnum <= 0)  // oob safety for switch below
            continue;

        for (int i = 0; i < 2; i++)
        {
            switch (tileGetMapping(s.picnum-1+i))
            {
                case DIPSWITCH__:
                case DIPSWITCH2__:
                case FRANKENSTINESWITCH__:
                case HANDSWITCH__:
                case LIGHTSWITCH__:
                case LIGHTSWITCH2__:
                case LOCKSWITCH1__:
                case POWERSWITCH1__:
                case POWERSWITCH2__:
                case PULLSWITCH__:
                case SLOTDOOR__:
                case SPACEDOORSWITCH__:
                case SPACELIGHTSWITCH__:
                    // the lower code only for the 'on' state (*)
                    if (i == 0)
                    {
                        uint16_t const tag = s.lotag;
                        tagbitmap[tag >> 3] |= 1 << (tag & 7);
                    }

                    break;
            }
        }
    }

    // initially 'on' SE 12 light (*)
    for (int nextSprite, SPRITES_OF_STAT_SAFE(STAT_EFFECTOR, j, nextSprite))
    {
        uint16_t const tag = sprite[j].hitag;

        if (sprite[j].lotag == SE_12_LIGHT_SWITCH && tagbitmap[tag>>3] & pow2char[tag&7])
            actor[j].t_data[0] = 1;
    }

    DO_FREE_AND_NULL(tagbitmap);
}

static void G_SetupSpecialWalls(void)
{
    g_mirrorCount = 0;

    for (int i = 0; i < numwalls; i++)
    {
        auto &w = wall[i];

        if (w.overpicnum == MIRROR && (w.cstat & 32) != 0)
        {
            int const nextSectnum = w.nextsector;

            if ((nextSectnum >= 0) && sector[nextSectnum].ceilingpicnum != MIRROR)
            {
                if (g_mirrorCount > 63)
                {
                    G_GameExit("Too many mirrors (64 max.)");
                }

                sector[nextSectnum].ceilingpicnum = MIRROR;
                sector[nextSectnum].floorpicnum   = MIRROR;
                g_mirrorWall[g_mirrorCount]       = i;
                g_mirrorSector[g_mirrorCount]     = nextSectnum;
                g_mirrorCount++;
                continue;
            }
        }

        if (g_animWallCnt >= MAXANIMWALLS)
        {
            Bsprintf(tempbuf, "Too many 'anim' walls (%d max).", MAXANIMWALLS);
            G_GameExit(tempbuf);
        }

        auto &aw = animwall[g_animWallCnt];

        aw.tag     = 0;
        aw.wallnum = 0;

        switch (tileGetMapping(G_GetForcefieldPicnum(i)))
        {
            case FANSHADOW__:
            case FANSPRITE__:
                // w.cstat |= 65;
                aw.wallnum = i;
                g_animWallCnt++;
                break;

            case W_FORCEFIELD__:
                if (w.overpicnum == W_FORCEFIELD__)
                {
                    for (int j = 0; j < 3; j++)
                        tloadtile(W_FORCEFIELD + j, 0);
                }

                if (w.shade > 31)
                    w.cstat = 0;
                else
                    w.cstat |= FORCEFIELD_CSTAT | CSTAT_WALL_BLOCK;

                if (w.lotag && w.nextwall >= 0)
                    wall[w.nextwall].lotag = w.lotag;

                fallthrough__;
            case BIGFORCE__:
                aw.wallnum = i;
                g_animWallCnt++;
                continue;
        }

        w.extra = -1;

        switch (tileGetMapping(w.picnum))
        {
#ifndef EDUKE32_STANDALONE
            case FEMPIC1__:
            case FEMPIC2__:
            case FEMPIC3__:
                w.extra = w.picnum;

                if (ud.lockout)
                    w.picnum = (w.picnum == FEMPIC1) ? BLANKSCREEN : SCREENBREAK6;

                aw.tag     = w.picnum;
                aw.wallnum = i;
                g_animWallCnt++;
                break;
#endif

            case W_TECHWALL1__:
            case W_TECHWALL2__:
            case W_TECHWALL3__:
            case W_TECHWALL4__:
                aw.wallnum = i;
                g_animWallCnt++;
                break;
            case SCREENBREAK6__:
            case SCREENBREAK7__:
            case SCREENBREAK8__:
                for (int j = SCREENBREAK6; j < SCREENBREAK9; j++)
                    tloadtile(j, 0);

                aw.tag     = -1;
                aw.wallnum = i;
                g_animWallCnt++;
                break;

            case SCREENBREAK1__:
            case SCREENBREAK2__:
            case SCREENBREAK3__:
            case SCREENBREAK4__:
            case SCREENBREAK5__:
            //
            case SCREENBREAK9__:
            case SCREENBREAK10__:
            case SCREENBREAK11__:
            case SCREENBREAK12__:
            case SCREENBREAK13__:
            case SCREENBREAK14__:
            case SCREENBREAK15__:
            case SCREENBREAK16__:
            case SCREENBREAK17__:
            case SCREENBREAK18__:
            case SCREENBREAK19__:
                aw.tag     = w.picnum;
                aw.wallnum = i;
                g_animWallCnt++;
                break;
        }
    }

    // Invalidate textures in sector behind mirror
    for (int i = 0; i < g_mirrorCount; i++)
    {
        int const startWall = sector[g_mirrorSector[i]].wallptr;
        int const endWall   = startWall + sector[g_mirrorSector[i]].wallnum;

        for (int j = startWall; j < endWall; j++)
        {
            wall[j].picnum = wall[j].overpicnum = MIRROR;

            if (wall[g_mirrorWall[i]].pal == 4)
                wall[j].pal = 4;
        }
    }
}

static void A_MaybeProcessEffector(int spriteNum)
{
    switch (tileGetMapping(PN(spriteNum)))
    {
        case ACTIVATOR__:
        case ACTIVATORLOCKED__:
        case LOCATORS__:
        case MASTERSWITCH__:
        case MUSICANDSFX__:
        case RESPAWN__:
        case SECTOREFFECTOR__:
        case TOUCHPLATE__:
            sprite[spriteNum].cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN | CSTAT_SPRITE_ALIGNMENT_MASK);
            break;

        case GPSPEED__:
            // DELETE_AFTER_LOADACTOR. Must not change statnum.
            sector[SECT(spriteNum)].extra = SLT(spriteNum);
            break;

        case CYCLER__:
        {
            // DELETE_AFTER_LOADACTOR. Must not change statnum.
            if (g_cyclerCnt >= MAXCYCLERS)
            {
                Bsprintf(tempbuf, "\nToo many cycling sectors (%d max).", MAXCYCLERS);
                G_GameExit(tempbuf);
            }

            auto &cycler = g_cyclers[g_cyclerCnt];

            cycler[0] = SECT(spriteNum);
            cycler[1] = SLT(spriteNum);
            cycler[2] = SS(spriteNum);
            cycler[3] = sector[SECT(spriteNum)].floorshade;
            cycler[4] = SHT(spriteNum);
            cycler[5] = (SA(spriteNum) == 1536);

            g_cyclerCnt++;
            break;
        }
    }
}

static void G_SpawnAllSprites()
{
    // I don't know why this is separated, but I have better things to do than combine them and see what happens
    for (int i = 0; i < MAXSPRITES; i++)
    {
        if (sprite[i].statnum < MAXSTATUS && (PN(i) != SECTOREFFECTOR || SLT(i) != SE_14_SUBWAY_CAR))
            A_Spawn(-1, i);
    }

    for (int i = 0; i < MAXSPRITES; i++)
    {
        if (sprite[i].statnum < MAXSTATUS && PN(i) == SECTOREFFECTOR && SLT(i) == SE_14_SUBWAY_CAR)
            A_Spawn(-1, i);
    }
}

static void G_DeleteTempEffectors()
{
    for (int nextSprite, SPRITES_OF_STAT_SAFE(STAT_DEFAULT, i, nextSprite))
    {
        switch (tileGetMapping(PN(i)))
        {
            case GPSPEED__:
            case CYCLER__: A_DeleteSprite(i); break;
        }
    }
}

static void prelevel(int g)
{
    //Bmemset(zhit, 0, sizeof(zhit));
    Bmemset(show2dsector, 0, sizeof(show2dsector));
#ifdef LEGACY_ROR
    Bmemset(ror_protectedsectors, 0, MAXSECTORS);
#endif
    g_cloudCnt = 0;

    P_PrepForNewLevel(0, g);
    G_SetupGlobalPsky();

    // Reset actor vars for all sprites, regardless of statnum.
    for (int i = 0; i < Numsprites; i++)
        A_ResetVars(i);

    VM_OnEvent(EVENT_PRELEVEL);

    int missedCloudSectors = 0;

    auto &p0 = *g_player[0].ps;

    for (int i = 0; i < numsectors; i++)
    {
        auto &s = sector[i];

        s.extra = 256;

        switch (s.lotag)
        {
        case ST_20_CEILING_DOOR:
        case ST_22_SPLITTING_DOOR:
            if (s.floorz > s.ceilingz)
                s.lotag |= 32768u;
            continue;
        }

        if (s.ceilingstat&1)
        {
            if (s.ceilingpicnum == CLOUDYSKIES)
            {
                if (g_cloudCnt < ARRAY_SSIZE(g_cloudSect))
                    g_cloudSect[g_cloudCnt++] = i;
                else
                    missedCloudSectors++;
            }

            if (p0.parallax_sectnum == -1)
                p0.parallax_sectnum = i;
        }

        if (s.lotag == 32767) //Found a secret room
        {
            p0.max_secret_rooms++;
            continue;
        }
    }

    if (missedCloudSectors > 0)
        LOG_F(WARNING, "Map has %d unhandled CLOUDYSKIES ceilings.", missedCloudSectors);

    // NOTE: must be safe loop because callbacks could delete sprites.
    for (int nextSprite, SPRITES_OF_STAT_SAFE(STAT_DEFAULT, i, nextSprite))
    {
        A_LoadActor(i);
        VM_OnEvent(EVENT_LOADACTOR, i);

        A_MaybeProcessEffector(i);
    }

    // Delete some effector / effector modifier sprites AFTER the loop running
    // the LOADACTOR events. DELETE_AFTER_LOADACTOR.
    G_DeleteTempEffectors();

    G_SpawnAllSprites();
    G_SetupRotfixedSprites();
    G_SetupLightSwitches();
    G_SetupSpecialWalls();
}

void G_PlayE4Cutscene(void)
{
    S_PlaySpecialMusicOrNothing(MUS_BRIEFING);

    renderFlushPerms();
    videoSetViewableArea(0, 0, xdim-1, ydim-1);
    videoClearViewableArea(0L);
    videoNextPage();

    if (Anim_Play("vol41a.anm"))
        goto end_vol4a;

    videoClearViewableArea(0L);
    videoNextPage();

    if (Anim_Play("vol42a.anm"))
        goto end_vol4a;

    videoClearViewableArea(0L);
    videoNextPage();

    Anim_Play("vol43a.anm");

end_vol4a:
    videoClearViewableArea(0L);
    videoNextPage();

    FX_StopAllSounds();
}

void G_NewGame(int volumeNum, int levelNum, int skillNum)
{
    auto &p0 = *g_player[0].ps;

    gameHandleEvents();

    if (ud.skill_voice > 0 && ud.config.SoundToggle)
    {
        // this is not an error
        while (ud.skill_voice > 0 && !FX_SoundActive(ud.skill_voice)) gameHandleEvents();
        while (ud.skill_voice > 0 && FX_SoundActive(ud.skill_voice)) gameHandleEvents();
    }

    S_PauseSounds(false);
    S_StopAllSounds();
    S_Cleanup();

    ready2send = 0;

    if (ud.m_recstat != 2 && ud.last_level != -1 && !VM_OnEventWithReturn(EVENT_EXITGAMESCREEN, g_player[myconnectindex].ps->i, myconnectindex, 0)
        && (g_netServer || ud.multimode > 1) && (ud.coop & GAMETYPE_SCORESHEET))
        G_BonusScreen(1);

    g_showShareware = GAMETICSPERSEC*34;

    ud.from_bonus    = 0;
    ud.last_level    = -1;
    ud.level_number  = levelNum;
    ud.player_skill  = skillNum;
    ud.secretlevel   = 0;
    ud.skill_voice   = -1;
    ud.volume_number = volumeNum;

    g_lastAutoSaveArbitraryID = -1;
    g_lastautosave.reset();
    g_lastusersave.reset();
    g_quickload = nullptr;

    // we don't want the intro to play after the multiplayer setup screen
    if ((!g_netServer && ud.multimode < 2) && !Menu_HaveUserMap()
        && !VM_OnEventWithReturn(EVENT_NEWGAMESCREEN, g_player[myconnectindex].ps->i, myconnectindex, 0)
        && !levelNum && volumeNum == 3 && !ud.lockout && !(G_GetLogoFlags() & LOGO_NOE4CUTSCENE))
        G_PlayE4Cutscene();

#ifdef EDUKE32_TOUCH_DEVICES
    p0.zoom = 360;
#else
    p0.zoom = 768;
#endif
    p0.gm = 0;

    Menu_Close(0);

    Gv_ResetVars();
    Gv_InitWeaponPointers();
    Gv_RefreshPointers();
    Gv_ResetSystemDefaults();

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

    if (ud.m_coop != 1)
    {
        for (int weaponNum = 0; weaponNum < MAX_WEAPONS; weaponNum++)
        {
            if (PWEAPON(0, weaponNum, WorksLike) == PISTOL_WEAPON)
            {
                p0.curr_weapon = weaponNum;
                p0.gotweapon |= (1 << weaponNum);
                p0.ammo_amount[weaponNum] = min<int16_t>(p0.max_ammo_amount[weaponNum], 48);
            }
            else if (PWEAPON(0, weaponNum, WorksLike) == KNEE_WEAPON || PWEAPON(0, weaponNum, WorksLike) == HANDREMOTE_WEAPON)
                p0.gotweapon |= (1 << weaponNum);
        }

        p0.last_weapon = -1;
    }

    display_mirror = 0;

    VM_OnEvent(EVENT_NEWGAME, g_player[screenpeek].ps->i, screenpeek);
}

static void G_CollectSpawnPoints(int gameMode)
{
    g_playerSpawnCnt = 0;
    //    circ = 2048/ud.multimode;

    for (int pindex = 0, pal = 9, nexti, SPRITES_OF_STAT_SAFE(STAT_PLAYER, i, nexti))
    {
        if (g_playerSpawnCnt == MAXPLAYERS)
            G_GameExit("\nToo many player sprites (max 16.)");

        auto &s     = sprite[i];
        auto &spawn = g_playerSpawnPoints[g_playerSpawnCnt];

        spawn.xyz  = s.xyz;
        spawn.ang  = s.ang;
        spawn.sect = s.sectnum;

        g_playerSpawnCnt++;

        if (pindex >= MAXPLAYERS)
        {
            A_DeleteSprite(i);
            i = nexti;
            continue;
        }

        s.clipdist = 64;
        s.owner    = i;
        s.shade    = 0;
        s.xoffset  = 0;
        s.xrepeat  = 42;
        s.yrepeat  = 36;

        s.cstat
        = (pindex < (!g_fakeMultiMode ? numplayers : ud.multimode)) ? CSTAT_SPRITE_BLOCK + CSTAT_SPRITE_BLOCK_HITSCAN : CSTAT_SPRITE_INVISIBLE;

        auto &plr = g_player[pindex];
        auto &p   = *plr.ps;

        if ((gameMode & MODE_EOL) != MODE_EOL || p.last_extra == 0)
        {
            p.last_extra = p.max_player_health;
            s.extra      = p.max_player_health;
        }
        else
            s.extra = p.last_extra;

        s.yvel = pindex;

        if (!plr.pcolor && (g_netServer || ud.multimode > 1) && !(g_gametypeFlags[ud.coop] & GAMETYPE_TDM))
        {
            if (s.pal == 0)
            {
                for (int TRAVERSE_CONNECT(k))
                {
                    if (pal == g_player[k].ps->palookup)
                    {
                        if (++pal > 16)
                            pal = 9;
                        k = 0;
                    }
                }

                plr.pcolor = s.pal = p.palookup = pal++;

                if (pal > 16)
                    pal = 9;
            }
            else
                plr.pcolor = p.palookup = s.pal;
        }
        else
        {
            int k = plr.pcolor;

            if (g_gametypeFlags[ud.coop] & GAMETYPE_TDM)
            {
                k      = G_GetTeamPalette(plr.pteam);
                p.team = plr.pteam;
            }

            s.pal = p.palookup = k;
        }

        p.frag_ps = pindex;

        actor[i].htowner = p.i = i;
        actor[i].bpos = p.opos = p.pos = s.xyz;
        p.bobpos = s.xy;

        p.oq16ang = p.q16ang = fix16_from_int(s.ang);

        p.cursectnum = s.sectnum;

        pindex++;

        i = nexti;
    }
}

static void G_ResetAllPlayers(void)
{
    uint8_t aimmode[MAXPLAYERS], autoaim[MAXPLAYERS], wswitch[MAXPLAYERS];
    DukeStatus_t tsbar[MAXPLAYERS];

    if (g_player[0].ps->cursectnum >= 0)  // < 0 may happen if we start a map in void space (e.g. testing it)
    {
        A_InsertSprite(g_player[0].ps->cursectnum,g_player[0].ps->pos.x,g_player[0].ps->pos.y,g_player[0].ps->pos.z,
                       APLAYER,0,0,0,fix16_to_int(g_player[0].ps->q16ang),0,0,0,10);
    }

    if (ud.recstat != 2)
    {
        for (int TRAVERSE_CONNECT(i))
        {
            auto &p = *g_player[i].ps;

            aimmode[i] = p.aim_mode;
            autoaim[i] = p.auto_aim;
            wswitch[i] = p.weaponswitch;

            if ((g_netServer || ud.multimode > 1) && (g_gametypeFlags[ud.coop] & GAMETYPE_PRESERVEINVENTORYDEATH) && ud.last_level >= 0)
            {
                for (int j = 0; j < MAX_WEAPONS; j++)
                    tsbar[i].ammo_amount[j] = p.ammo_amount[j];

                tsbar[i].gotweapon   = p.gotweapon;
                tsbar[i].curr_weapon = p.curr_weapon;
                tsbar[i].inven_icon  = p.inven_icon;
                Bmemcpy(tsbar[i].inv_amount, p.inv_amount, sizeof(tsbar[i].inv_amount));
            }
        }
    }

    P_ResetPlayer(0);

    for (int TRAVERSE_CONNECT(i))
    {
        auto &plr = g_player[i];

        Bmemset(plr.frags, 0, sizeof(plr.frags));

        if (i != 0)
            Bmemcpy(plr.ps, g_player[0].ps, sizeof(DukePlayer_t));
    }

    if (ud.recstat != 2)
    {
        for (int TRAVERSE_CONNECT(i))
        {
            auto &p = *g_player[i].ps;

            p.aim_mode     = aimmode[i];
            p.auto_aim     = autoaim[i];
            p.weaponswitch = wswitch[i];

            if ((g_netServer || ud.multimode > 1) && (g_gametypeFlags[ud.coop] & GAMETYPE_PRESERVEINVENTORYDEATH) && ud.last_level >= 0)
            {
                for (int j = 0; j < MAX_WEAPONS; j++)
                    p.ammo_amount[j] = tsbar[i].ammo_amount[j];

                p.gotweapon   = tsbar[i].gotweapon;
                p.curr_weapon = tsbar[i].curr_weapon;
                p.inven_icon  = tsbar[i].inven_icon;
                Bmemcpy(p.inv_amount, tsbar[i].inv_amount, sizeof(tsbar[i].inv_amount));
            }
        }
    }

    // take away the pistol if the player spawns on any of these textures
    for (int TRAVERSE_CONNECT(i))
    {
        auto &p = *g_player[i].ps;

        if (p.cursectnum >= 0)
        {
            switch (tileGetMapping(sector[p.cursectnum].floorpicnum))
            {
                case HURTRAIL__:
                case FLOORSLIME__:
                case FLOORPLASMA__:
                    P_ResetWeapons(i);
                    P_ResetInventory(i);

                    p.ammo_amount[PISTOL_WEAPON] = 0;
                    p.gotweapon &= ~(1 << PISTOL_WEAPON);
                    p.curr_weapon  = KNEE_WEAPON;
                    p.kickback_pic = 0;

                    break;
            }
        }
    }
}

void G_ResetTimers(bool saveMoveCnt)
{
    g_lastInputTicks = 0;
    totalclock = g_cloudClock = ototalclock = lockclock = 0;
    ready2send = 1;
    g_levelTextTime = 85;

    if (!saveMoveCnt)
        g_moveThingsCount = 0;

    for (int vscrIndex = 0; vscrIndex < MAX_ACTIVE_VIEWSCREENS; vscrIndex++)
    {
        if (g_activeVscrSprite[vscrIndex] >= 0)
        {
            actor[g_activeVscrSprite[vscrIndex]].t_data[0] = 0;
            actor[g_activeVscrSprite[vscrIndex]].t_data[1] = -1;
        }
    }
}

void G_ClearFIFO(void)
{
    g_lastInputTicks = 0;
    localInput = {};
    Bmemset(&inputfifo, 0, sizeof(input_t) * MOVEFIFOSIZ * MAXPLAYERS);

    for (int p = 0; p < MAXPLAYERS; ++p)
    {
        auto &player = g_player[p];

        player.input   = {};
        player.vote    = 0;
        player.gotvote = 0;

        player.horizSkew        = 0;
        player.horizAngleAdjust = 0;
        player.lastViewUpdate   = 0;
    }
}

int G_FindLevelByFile(const char *fileName)
{
    int i = 0;

    for (auto &levelNum : g_mapInfo)
    {
        i++;

        if (levelNum.filename == NULL)
            continue;
        else if (!Bstrcasecmp(fileName, levelNum.filename))
            return i-1;
    }

    return -1;
}

#if 0
static void G_FadeLoad(int32_t r, int32_t g, int32_t b, int32_t start, int32_t end, int32_t step, int32_t ticwait, int32_t tc)
{
    int32_t m = (step < 0) ? -1 : 1;

    int32_t nexttic = totalclock;

    for (; m*start <= m*end; start += step)
    {
        while (totalclock < nexttic)
            sampletimer();
        nexttic += ticwait;

        if (KB_KeyPressed(sc_Space))
        {
            KB_ClearKeyDown(sc_Space);
            return;
        }

        setpalettefade(r,g,b,start);
        flushperms();
        G_DoLoadScreen(" ", tc);
    }
}
#endif

static int G_TryMapHack(const char *mhkfile)
{
    int const failure = engineLoadMHK(mhkfile);

    if (!failure)
        LOG_F(INFO, "Loaded %s", mhkfile);

    return failure;
}

static void G_LoadMapHack(char *outbuf, const char *filename)
{
    if (filename != NULL)
        Bstrcpy(outbuf, filename);

    append_ext_UNSAFE(outbuf, ".mhk");

    if (G_TryMapHack(outbuf) && usermaphacks != NULL)
    {
        auto pMapInfo = (usermaphack_t *)bsearch(&g_loadedMapHack, usermaphacks, num_usermaphacks,
                                                 sizeof(usermaphack_t), compare_usermaphacks);
        if (pMapInfo)
            G_TryMapHack(pMapInfo->mhkfile);
    }
}

// levnamebuf should have at least size BMAX_PATH
void G_SetupFilenameBasedMusic(char *nameBuf, const char *fileName)
{
    char *p;
    char const *exts[] = {
#ifdef HAVE_FLAC
        "flac",
#endif
#ifdef HAVE_VORBIS
        "ogg",
#endif
#ifdef HAVE_XMP
        "xm",
        "mod",
        "it",
        "s3m",
        "mtm",
#endif
        "mid"
    };

    Bstrncpy(nameBuf, fileName, BMAX_PATH);

    Bcorrectfilename(nameBuf, 0);

    if (NULL == (p = Bstrrchr(nameBuf, '.')))
    {
        p = nameBuf + Bstrlen(nameBuf);
        p[0] = '.';
    }

    for (auto & ext : exts)
    {
        buildvfs_kfd kFile;

        Bmemcpy(p+1, ext, Bstrlen(ext) + 1);

        if ((kFile = kopen4loadfrommod(nameBuf, 0)) != buildvfs_kfd_invalid)
        {
            kclose(kFile);
            realloc_copy(&g_mapInfo[USERMAPMUSICFAKESLOT].musicfn, nameBuf);
            return;
        }
    }

    char const * usermapMusic = g_mapInfo[MUS_USERMAP].musicfn;
    if (usermapMusic != nullptr)
    {
        realloc_copy(&g_mapInfo[USERMAPMUSICFAKESLOT].musicfn, usermapMusic);
        return;
    }

#ifndef EDUKE32_STANDALONE
    if (!FURY)
    {
        char const * e1l8 = g_mapInfo[7].musicfn;
        if (e1l8 != nullptr)
        {
            realloc_copy(&g_mapInfo[USERMAPMUSICFAKESLOT].musicfn, e1l8);
            return;
        }
    }
#endif
}

static void G_CheckIfStateless()
{
    for (bssize_t i = 0; i < (MAXVOLUMES * MAXLEVELS); i++)
    {
        map_t *const pMapInfo = &g_mapInfo[i];
        if (pMapInfo->savedstate != nullptr)
        {
            // buildprint("G_CheckIfStateless: no ", ud.volume_number, " ", ud.level_number, "\n");
            return;
        }
    }

    // buildprint("G_CheckIfStateless: yes ", ud.volume_number, " ", ud.level_number, "\n");
    ud.last_stateless_volume = ud.volume_number;
    ud.last_stateless_level = ud.level_number;
}

int G_EnterLevel(int gameMode)
{
    vote_map = vote_episode = voting = -1;

    ud.respawn_monsters  = ud.m_respawn_monsters;
    ud.respawn_items     = ud.m_respawn_items;
    ud.respawn_inventory = ud.m_respawn_inventory;
    ud.monsters_off      = ud.m_monsters_off;
    ud.coop              = ud.m_coop;
    ud.marker            = ud.m_marker;
    ud.ffire             = ud.m_ffire;
    ud.noexits           = ud.m_noexits;

    if ((gameMode & MODE_DEMO) != MODE_DEMO)
        ud.recstat = ud.m_recstat;
    if ((gameMode & MODE_DEMO) == 0 && ud.recstat == 2)
        ud.recstat = 0;

    if (g_networkMode != NET_DEDICATED_SERVER)
    {
        S_PauseSounds(false);
        S_StopAllSounds();
        S_ClearSoundLocks();
        FX_SetReverb(0);
        videoSetGameMode(ud.setup.fullscreen, ud.setup.xdim, ud.setup.ydim, ud.setup.bpp, upscalefactor);
    }

    if (Menu_HaveUserMap() || G_HaveUserMap())
    {
        Bcorrectfilename(boardfilename, 0);

        int levelNum = G_FindLevelByFile(boardfilename);

        if (levelNum != -1)
        {
            int volumeNum = levelNum;

            levelNum &= MAXLEVELS-1;
            volumeNum = (volumeNum - levelNum) / MAXLEVELS;

            ud.level_number = ud.m_level_number = levelNum;
            ud.volume_number = ud.m_volume_number = volumeNum;

            boardfilename[0] = 0;
        }
    }
    else
        boardfilename[0] = '\0';

    int const mapidx = (ud.volume_number * MAXLEVELS) + ud.level_number;

    Bassert((unsigned)mapidx < ARRAY_SIZE(g_mapInfo));

    auto &m = g_mapInfo[mapidx];

    if (VOLUMEONE || !Menu_HaveUserMap())
    {
        if (m.name == NULL || m.filename == NULL)
        {
            LOG_F(ERROR, "Map E%dL%d not defined!", ud.volume_number+1, ud.level_number+1);
            return 1;
        }
    }

    int const ssize = ud.screen_size;
    ud.screen_size = 0;

    G_DoLoadScreen("Loading map . . .", -1);
    G_UpdateScreenArea();
#ifdef POLYMER
    G_DeleteAllLights();
#endif

    ud.screen_size = ssize;

    G_UpdateAppTitle(Menu_HaveUserMap() ? boardfilename : m.name);

    auto   &p0 = *g_player[0].ps;
    int16_t playerAngle;

    char levelName[BMAX_PATH];
    NET_75_CHECK++; // a major problem with how STAT_NETALLOC works, is that loadboard loads sprites directly into the arrays and does not take from
                    // STAT_NETALLOC, even though the loaded sprites are very, very likely to be relevant to the netcode.

    if (!VOLUMEONE && G_HaveUserMap())
    {
        if (engineLoadBoard(boardfilename, 0, &p0.pos, &playerAngle, &p0.cursectnum) < 0)
        {
            LOG_F(ERROR, "Unable to load %s: file not found or has invalid version!", boardfilename);
            return 1;
        }

        G_LoadMapHack(levelName, boardfilename);
        G_SetupFilenameBasedMusic(levelName, boardfilename);
    }
    else if (engineLoadBoard(m.filename, VOLUMEONE, &p0.pos, &playerAngle, &p0.cursectnum) < 0)
    {
        LOG_F(ERROR, "Unable to load %s: file not found or has invalid version!", m.filename);
        return 1;
    }
    else
    {
        G_LoadMapHack(levelName, m.filename);
    }

    p0.q16ang = fix16_from_int(playerAngle);

    NET_75_CHECK++; // resetpspritevars attempts to insert player 0's sprite, which isn't going to work because we don't have
                    // the STAT_NETALLOC sprites allocated yet.

    Net_NotifyNewGame();

    prelevel(gameMode);

    G_AlignWarpElevators();
    G_ResetAllPlayers();
    G_CollectSpawnPoints(gameMode);

    ud.playerbest = CONFIG_GetMapBestTime(Menu_HaveUserMap() ? boardfilename : m.filename, g_loadedMapHack.md4);

    // G_FadeLoad(0,0,0, 252,0, -28, 4, -1);
    G_CacheMapData();
    // G_FadeLoad(0,0,0, 0,252, 28, 4, -2);

    if (ud.recstat != 2)
    {
        if (Menu_HaveUserMap())
        {
            S_PlayLevelMusicOrNothing(USERMAPMUSICFAKESLOT);
        }
        else if (g_mapInfo[g_musicIndex].musicfn == NULL || m.musicfn == NULL ||
            strcmp(g_mapInfo[g_musicIndex].musicfn, m.musicfn) || g_musicSize == 0 || ud.last_level == -1)
        {
            S_PlayLevelMusicOrNothing(mapidx);
        }
        else
        {
            S_ContinueLevelMusic();
        }
    }

    if (gameMode & (MODE_GAME|MODE_EOL))
    {
        for (int TRAVERSE_CONNECT(i))
        {
            g_player[i].ps->gm = MODE_GAME;
            Menu_Close(i);
        }
    }
    else if (gameMode & MODE_RESTART)
        g_player[myconnectindex].ps->gm = (ud.recstat == 2) ? MODE_DEMO : MODE_GAME;

    if ((ud.recstat == 1) && (gameMode&MODE_RESTART) != MODE_RESTART)
        G_OpenDemoWrite();

#ifndef EDUKE32_TOUCH_DEVICES
    if (VOLUMEONE && ud.level_number == 0 && ud.recstat != 2)
        P_DoQuote(QUOTE_F1HELP,g_player[myconnectindex].ps);
#endif

    //Net_NotifyNewGame();
    Net_ResetPrediction();

    //g_player[myconnectindex].ps->palette = palette;
    //G_FadePalette(0,0,0,0);
    P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 0);    // JBF 20040308
    P_UpdateScreenPal(g_player[myconnectindex].ps);
    renderFlushPerms();

    everyothertime = 0;
    g_globalRandom = 0;

    ud.last_level = ud.level_number+1;

    G_ClearFIFO();

    for (int i=g_interpolationCnt-1; i>=0; i--) bakipos[i] = *curipos[i];

    G_ResetTimers(0);  // Here we go

    Bmemcpy(currentboardfilename, boardfilename, BMAX_PATH);

    G_CheckIfStateless();

    for (int TRAVERSE_CONNECT(i))
    {
        if (!VM_OnEventWithReturn(EVENT_ENTERLEVEL, g_player[i].ps->i, i, 0))
            break;
    }

    if (G_HaveUserMap())
        OSD_Printf(OSDTEXT_YELLOW "User Map: %s\n", boardfilename);
    else if (FURY)
        OSD_Printf(OSDTEXT_YELLOW "Entering: %s\n", m.name);
    else
        OSD_Printf(OSDTEXT_YELLOW "E%dL%d: %s\n", ud.volume_number + 1, ud.level_number + 1, m.name);

    g_restorePalette = -1;

    G_UpdateScreenArea();
    videoClearViewableArea(0L);
    G_DrawBackground();
    G_DrawRooms(myconnectindex,65536);
    if (g_netClient || g_netServer) // [75] : Initialize map states after map load
    {
        Net_InitMapStateHistory();
        Net_AddWorldToInitialSnapshot();
    }

    Net_WaitForServer();
    return 0;
}

void G_FreeMapState(int levelNum)
{
    auto &board = g_mapInfo[levelNum];

    if (board.savedstate == NULL)
        return;

    for (int j=0; j<g_gameVarCount; j++)
    {
        if (aGameVars[j].flags & SAVEGAMEMAPSTATEVARSKIPMASK)
            continue;

        if (aGameVars[j].flags & (GAMEVAR_PERPLAYER|GAMEVAR_PERACTOR))
            ALIGNED_FREE_AND_NULL(board.savedstate->vars[j]);
    }

    for (int j=0; j<g_gameArrayCount; j++)
    {
        if (aGameArrays[j].flags & SAVEGAMEARRAYSKIPMASK)
            continue;

        if (aGameArrays[j].flags & GAMEARRAY_RESTORE)
            ALIGNED_FREE_AND_NULL(board.savedstate->arrays[j]);
    }

    ALIGNED_FREE_AND_NULL(board.savedstate);
}


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 premap_h_
#define premap_h_

#ifdef __cplusplus
extern "C" {
#endif

extern int32_t g_levelTextTime;
extern int32_t voting,vote_map,vote_episode;
extern palette_t CrosshairColors;
void G_SetupFilenameBasedMusic(char *nameBuf, const char *fileName);
int G_EnterLevel(int gameMode);
int G_FindLevelByFile(const char *fileName);
void G_CacheMapData(void);
void G_FreeMapState(int levelNum);
void G_NewGame(int volumeNum, int levelNum, int skillNum);
void G_ResetTimers(bool saveMoveCnt);
void G_SetCrosshairColor(int32_t r,int32_t g,int32_t b);
void G_UpdateScreenArea(void);
void G_SetViewportShrink(int32_t dir);
void P_MoveToRandomSpawnPoint(int playerNum);
void P_ResetExtents(DukePlayer_t* const pPlayer);
void P_ResetInventory(int playerNum);
void P_ResetMultiPlayer(int playerNum);
void P_ResetOffsets(DukePlayer_t* const pPlayer);
void P_ResetPlayer(int playerNum);
void P_ResetWeapons(int playerNum);
void G_ClearFIFO(void);
void G_ResetInterpolations(void);
int fragbarheight(void);

#ifdef __cplusplus
}
#endif

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2011 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 quotes_h_
#define quotes_h_

#define MAXQUOTES                   16384
#define MAXQUOTELEN                 128
#define OBITQUOTEINDEX              (MAXQUOTES-128)
#define SUICIDEQUOTEINDEX           (MAXQUOTES-32)

#define NOBETAQUOTE(x)              (DUKEBETA ? -1 : x)

#define QUOTE_SHOW_MAP_OFF          NOBETAQUOTE(1)
#define QUOTE_ACTIVATED             2
#define QUOTE_MEDKIT                3
#define QUOTE_LOCKED                4
#define QUOTE_CHEAT_EVERYTHING      5
#define QUOTE_BOOTS                 6
#define QUOTE_WASTED                7
#define QUOTE_UNLOCKED              8
#define QUOTE_FOUND_SECRET          9
#define QUOTE_SQUISHED              10
#define QUOTE_USED_STEROIDS         NOBETAQUOTE(12)
#define QUOTE_DEAD                  13
#define QUOTE_DEACTIVATED           15
#define QUOTE_CHEAT_GODMODE_ON      17
#define QUOTE_CHEAT_GODMODE_OFF     18
#define QUOTE_CROSSHAIR_OFF         NOBETAQUOTE(21)
#define QUOTE_CHEATS_DISABLED       22
#define QUOTE_MESSAGES_ON           23
#define QUOTE_MESSAGES_OFF          24
#define QUOTE_MUSIC                 26
#define QUOTE_CHEAT_STEROIDS        37
#define QUOTE_F1HELP                40
#define QUOTE_MOUSE_AIMING_OFF      44
#define QUOTE_HOLODUKE_ON           47
#define QUOTE_HOLODUKE_OFF          48
#define QUOTE_HOLODUKE_NOT_FOUND    49
#define QUOTE_JETPACK_NOT_FOUND     50
#define QUOTE_JETPACK_ON            52
#define QUOTE_JETPACK_OFF           53
#define QUOTE_NEED_BLUE_KEY         70
#define QUOTE_NEED_RED_KEY          71
#define QUOTE_NEED_YELLOW_KEY       72
#define QUOTE_WEAPON_LOWERED        73
#define QUOTE_WEAPON_RAISED         74
#define QUOTE_BOOTS_ON              75
#define QUOTE_SCUBA_ON              76
#define QUOTE_CHEAT_ALLEN           NOBETAQUOTE(79)
#define QUOTE_MIGHTY_FOOT           80
#define QUOTE_WEAPON_MODE_OFF       NOBETAQUOTE(82)
#define QUOTE_MAP_FOLLOW_OFF        83
#define QUOTE_RUN_MODE_OFF          85
#define QUOTE_JETPACK               88
#define QUOTE_SCUBA                 89
#define QUOTE_STEROIDS              90
#define QUOTE_HOLODUKE              91
#define QUOTE_CHEAT_TODD            NOBETAQUOTE(99)
#define QUOTE_CHEAT_UNLOCK          NOBETAQUOTE(100)
#define QUOTE_NVG                   101
#define QUOTE_WEREGONNAFRYYOURASS   102
#define QUOTE_SCREEN_SAVED          103
#define QUOTE_CHEAT_BETA            NOBETAQUOTE(105)
#define QUOTE_NVG_OFF               107
#define QUOTE_VIEW_MODE_OFF         NOBETAQUOTE(109)
#define QUOTE_SHOW_MAP_ON           NOBETAQUOTE(111)
#define QUOTE_CHEAT_NOCLIP          NOBETAQUOTE(113)
#define QUOTE_SAVE_BAD_VERSION      114
#define QUOTE_RESERVED              115
#define QUOTE_RESERVED2             116
#define QUOTE_RESERVED3             117
#define QUOTE_SAVE_DEAD             NOBETAQUOTE(118)
#define QUOTE_CHEAT_ALL_WEAPONS     NOBETAQUOTE(119)
#define QUOTE_CHEAT_ALL_INV         NOBETAQUOTE(120)
#define QUOTE_CHEAT_ALL_KEYS        NOBETAQUOTE(121)
#define QUOTE_RESERVED4             122
#define QUOTE_SAVE_BAD_PLAYERS      124

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

#include "vfs.h"

static int32_t rts_numlumps;
static void  **rts_lumpcache;
static lumpinfo_t *rts_lumpinfo;
static int32_t RTS_Started = FALSE;

char rts_lumplockbyte[11];

/*
= RTS_AddFile
=
= All files are optional, but at least one file must be found
= Files with a .rts extension are wadlink files with multiple lumps
= Other files are single lumps with the base filename for the lump name
*/
static int32_t RTS_AddFile(const char *filename)
{
    wadinfo_t  header;
    int32_t i, length, startlump;
    filelump_t *fileinfo, *fileinfoo;

    // read the entire file in
    //      FIXME: shared opens

    buildvfs_kfd handle = kopen4loadfrommod(filename, 0);

    if (handle == buildvfs_kfd_invalid)
        return -1;
    else
        LOG_F(INFO, "Loaded %s", filename);

    startlump = rts_numlumps;

    // WAD file
    i = kread(handle, &header, sizeof(header));
    if (i != sizeof(header) || Bmemcmp(header.identification, "IWAD", 4))
    {
        LOG_F(WARNING, "Unable to load %s: file is truncated or missing IWAD id.", filename);
        kclose(handle);
        return -1;
    }

    header.numlumps = B_LITTLE32(header.numlumps);
    header.infotableofs = B_LITTLE32(header.infotableofs);

    length = header.numlumps*sizeof(filelump_t);
    fileinfo = fileinfoo = (filelump_t *)Xmalloc(length);

    klseek(handle, header.infotableofs, SEEK_SET);
    kread(handle, fileinfo, length);

    {
        lumpinfo_t *lump_p = (lumpinfo_t *)Xrealloc(
            rts_lumpinfo, (rts_numlumps + header.numlumps)*sizeof(lumpinfo_t));

        rts_lumpinfo = lump_p;
    }

    rts_numlumps += header.numlumps;

    for (i=startlump; i<rts_numlumps; i++, fileinfo++)
    {
        lumpinfo_t *lump = &rts_lumpinfo[i];

        lump->handle = handle;  // NOTE: cache1d-file is not closed!
        lump->position = B_LITTLE32(fileinfo->filepos);
        lump->size = B_LITTLE32(fileinfo->size);

        Bstrncpy(lump->name, fileinfo->name, 8);
    }

    Xfree(fileinfoo);

    return 0;
}

void RTS_Init(const char *filename)
{
    // open all the files, load headers, and count lumps

    rts_numlumps = 0;
    rts_lumpinfo = NULL;   // will be realloced as lumps are added

    if (RTS_AddFile(filename))
        return;

    if (rts_numlumps == 0)
        return;

    rts_lumpcache = (void **)Xcalloc(rts_numlumps, sizeof(rts_lumpcache[0]));

    RTS_Started = TRUE;
}

int32_t RTS_IsInitialized(void)
{
    return rts_numlumps > 0;
}

#define RTS_BAD_LUMP(lump) ((uint32_t)lump >= (uint32_t)rts_numlumps)

int32_t RTS_SoundLength(int32_t lump)
{
    lump++;

    return RTS_BAD_LUMP(lump) ? 0 : rts_lumpinfo[lump].size;
}

/* Loads the lump into the given buffer, which must be >= RTS_SoundLength() */
static void RTS_ReadLump(int32_t lump, void *dest)
{
    lumpinfo_t *l = &rts_lumpinfo[lump];

    klseek(l->handle, l->position, SEEK_SET);
    kread(l->handle, dest, l->size);
}

void *RTS_GetSound(int32_t lump)
{
    lump++;

    if (RTS_BAD_LUMP(lump))
        return NULL;

    if (rts_lumpcache[lump] == NULL)
    {
        rts_lumplockbyte[lump] = CACHE1D_LOCKED;
        g_cache.allocateBlock((intptr_t *)&rts_lumpcache[lump], RTS_SoundLength(lump-1), &rts_lumplockbyte[lump]);  // JBF 20030910: char * => int32_t *
        RTS_ReadLump(lump, rts_lumpcache[lump]);
    }
    else
    {
        if (rts_lumplockbyte[lump] < CACHE1D_LOCKED)
            rts_lumplockbyte[lump] = CACHE1D_LOCKED;
        else
            rts_lumplockbyte[lump]++;
    }

    return rts_lumpcache[lump];
}


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

//***************************************************************************
//
//    RTS.H
//
//***************************************************************************

#ifndef rts_public_
#define rts_public_

extern char rts_lumplockbyte[11];

/* Files with a .rts extension are idlink files with multiple lumps */
void RTS_Init(const char *filename);

int32_t RTS_IsInitialized(void);

/* Returns the buffer size needed to load the given lump */
int32_t RTS_SoundLength(int32_t lump);

void *RTS_GetSound(int32_t lump);

#endif