// Ken Silverman's official web site: "http://www.advsys.net/ken"
// See the included license file "BUILDLIC.TXT" for license info.
//
// This file has been modified from Ken Silverman's original release
// by Jonathon Fowler (jf@jonof.id.au)

#include "compat.h"
#include "build.h"
#include "names.h"
#include "pragmas.h"
#include "cache1d.h"
#include "game.h"
#include "osd.h"
#include "mmulti.h"
#include "common.h"

#include "renderlayer.h"

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

#include "common_game.h"

const char *AppProperName = APPNAME;
const char *AppTechnicalName = APPBASENAME;

#define SETUPFILENAME APPBASENAME ".cfg"
char setupfilename[BMAX_PATH] = SETUPFILENAME;

#define TIMERINTSPERSECOND 140 //280
#define MOVESPERSECOND 40
#define TICSPERFRAME 3
#define MOVEFIFOSIZ 256
#define EYEHEIGHT (32<<8)   //Normally (32<<8), (51<<8) to make mirrors happy

#define TILE_TILT           (MAXTILES-2)


static int32_t setsprite_eyeheight(int16_t spritenum, const vec3_t *pos) ATTRIBUTE((nonnull(2)));
static int32_t setsprite_eyeheight(int16_t spritenum, const vec3_t *pos)
{
    vec3_t eyepos = *pos;
    eyepos.z += EYEHEIGHT;
    return setsprite(spritenum, &eyepos);
}

static FORCE_INLINE int32_t ksqr(int32_t a) { return a * a; }

// declared in sound.c
void initsb(char,char,int,char,char,char,char);
void uninitsb(void);
void setears(int,int,int,int);
void wsayfollow(char const *,int,int,int *,int *,char);
void wsay(char const *,int,int,int);
void loadwaves(char const *);
int loadsong(char const *);
void musicon(void);
void musicoff(void);
void refreshaudio(void);

// declared in config.c
int Ken_loadsetup(const char *);
int Ken_writesetup(const char *);

/***************************************************************************
    KEN'S TAG DEFINITIONS:      (Please define your own tags for your games)

 sector[?].lotag = 0   Normal sector
 sector[?].lotag = 1   If you are on a sector with this tag, then all sectors
                       with same hi tag as this are operated.  Once.
 sector[?].lotag = 2   Same as sector[?].tag = 1 but this is retriggable.
 sector[?].lotag = 3   A really stupid sector that really does nothing now.
 sector[?].lotag = 4   A sector where you are put closer to the floor
                       (such as the slime in DOOM1.DAT)
 sector[?].lotag = 5   A really stupid sector that really does nothing now.
 sector[?].lotag = 6   A normal door - instead of pressing D, you tag the
                       sector with a 6.  The reason I make you edit doors
                       this way is so that can program the doors
                       yourself.
 sector[?].lotag = 7   A door the goes down to open.
 sector[?].lotag = 8   A door that opens horizontally in the middle.
 sector[?].lotag = 9   A sliding door that opens vertically in the middle.
                       -Example of the advantages of not using BSP tree.
 sector[?].lotag = 10  A warping sector with floor and walls that shade.
 sector[?].lotag = 11  A sector with all walls that do X-panning.
 sector[?].lotag = 12  A sector with walls using the dragging function.
 sector[?].lotag = 13  A sector with some swinging doors in it.
 sector[?].lotag = 14  A revolving door sector.
 sector[?].lotag = 15  A subway track.
 sector[?].lotag = 16  A true double-sliding door.

    wall[?].lotag = 0   Normal wall
    wall[?].lotag = 1   Y-panning wall
    wall[?].lotag = 2   Switch - If you flip it, then all sectors with same hi
                        tag as this are operated.
    wall[?].lotag = 3   Marked wall to detemine starting dir. (sector tag 12)
    wall[?].lotag = 4   Mark on the shorter wall closest to the pivot point
                        of a swinging door. (sector tag 13)
    wall[?].lotag = 5   Mark where a subway should stop. (sector tag 15)
    wall[?].lotag = 6   Mark for true double-sliding doors (sector tag 16)
    wall[?].lotag = 7   Water fountain
    wall[?].lotag = 8   Bouncy wall!

 sprite[?].lotag = 0   Normal sprite
 sprite[?].lotag = 1   If you press space bar on an AL, and the AL is tagged
                       with a 1, he will turn evil.
 sprite[?].lotag = 2   When this sprite is operated, a bomb is shot at its
                       position.
 sprite[?].lotag = 3   Rotating sprite.
 sprite[?].lotag = 4   Sprite switch.
 sprite[?].lotag = 5   Basketball hoop score.

    KEN'S STATUS DEFINITIONS:  (Please define your own statuses for your games)
 status = 0            Inactive sprite
 status = 1            Active monster sprite
 status = 2            Monster that becomes active only when it sees you
 status = 3            Smoke on the wall for chainguns
 status = 4            Splashing sprites (When you shoot slime)
 status = 5            Explosion!
 status = 6            Travelling bullet
 status = 7            Bomb sprial-out explosion
 status = 8            Player!
 status = 9            EVILALGRAVE shrinking list
 status = 10           EVILAL list
 status = 11           Sprite respawning list
 status = 12           Sprite which does not respawn (Andy's addition)
 status = MAXSTATUS    Non-existent sprite (this will be true for your
                       code also)
**************************************************************************/

typedef struct
{
    signed char fvel, svel, avel;
    short bits;
} input;

static int screentilt = 0, oscreentilt = 0;


static int fvel, svel, avel;
static int fvel2, svel2, avel2;

unsigned char option[NUMOPTIONS] = {0,0,1,1,0,0,1,1+4+(6<<4)};
unsigned char keys[NUMGAMEKEYS] =
{
    0xc8,0xd0,0xcb,0xcd,0x2a,0x9d,0x1d,0x39,
    0x1e,0x2c,0xd1,0xc9,0x33,0x34,
    0x9c,0x1c,0xd,0xc,0xf,0x29
};

extern "C" {
int xdimgame = 320, ydimgame = 200, bppgame = 8, xdim2d = 640, ydim2d = 480;    // JBF 20050318: config.c expects to find these
int forcesetup = 1;
}

static int digihz[8] = {6000,8000,11025,16000,22050,32000,44100,48000};

static char frame2draw[MAXPLAYERS];
static int frameskipcnt[MAXPLAYERS];

#define LAVASIZ 128
#define LAVALOGSIZ 7
#define LAVAMAXDROPS 32
static char lavabakpic[(LAVASIZ+4)*(LAVASIZ+4)], lavainc[LAVASIZ];
static int lavanumdrops, lavanumframes;
static int lavadropx[LAVAMAXDROPS], lavadropy[LAVAMAXDROPS];
static int lavadropsiz[LAVAMAXDROPS], lavadropsizlookup[LAVAMAXDROPS];
static int lavaradx[24][96], lavarady[24][96], lavaradcnt[32];

//Shared player variables
static vec3_t pos[MAXPLAYERS];
static int horiz[MAXPLAYERS], zoom[MAXPLAYERS], hvel[MAXPLAYERS];
static short ang[MAXPLAYERS], cursectnum[MAXPLAYERS], ocursectnum[MAXPLAYERS];
static short playersprite[MAXPLAYERS], deaths[MAXPLAYERS];
static int lastchaingun[MAXPLAYERS];
static int health[MAXPLAYERS], flytime[MAXPLAYERS];
static short oflags[MAXPLAYERS];
static short numbombs[MAXPLAYERS];
static short numgrabbers[MAXPLAYERS];   // Andy did this
static short nummissiles[MAXPLAYERS];   // Andy did this
static char dimensionmode[MAXPLAYERS];
static char revolvedoorstat[MAXPLAYERS];
static short revolvedoorang[MAXPLAYERS], revolvedoorrotang[MAXPLAYERS];
static int revolvedoorx[MAXPLAYERS], revolvedoory[MAXPLAYERS];

static int nummoves;
// Bug: NUMSTATS used to be equal to the greatest tag number,
// so that the last statrate[] entry was random memory junk
// because stats 0-NUMSTATS required NUMSTATS+1 bytes.   -Andy
#define NUMSTATS 13
static signed char statrate[NUMSTATS] = {-1,0,-1,0,0,0,1,3,0,3,15,-1,-1};

//Input structures
static int locselectedgun, locselectedgun2;
static input loc, oloc, loc2;
static input ffsync[MAXPLAYERS], osync[MAXPLAYERS], ssync[MAXPLAYERS];
//Input faketimerhandler -> movethings fifo
static int movefifoplc, movefifoend[MAXPLAYERS];
static input baksync[MOVEFIFOSIZ][MAXPLAYERS];
//Game recording variables
static int reccnt, recstat = 1;
static input recsync[16384][2];

//static int myminlag[MAXPLAYERS], mymaxlag, otherminlag, bufferjitter = 1;
static signed char otherlag[MAXPLAYERS] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
static int averagelag[MAXPLAYERS] = {512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512};

static int fakemovefifoplc;
static vec3_t my, omy;
static int myzvel;
static int myhoriz, omyhoriz;
static short myang, omyang, mycursectnum;
static vec3_t mybak[MOVEFIFOSIZ];
static int myhorizbak[MOVEFIFOSIZ];
static short myangbak[MOVEFIFOSIZ];

//GAME.C sync state variables
static char syncstat, syncval[MOVEFIFOSIZ], othersyncval[MOVEFIFOSIZ];
static int syncvaltottail, syncvalhead, othersyncvalhead, syncvaltail;

static char detailmode = 0, ready2send = 0;
static int ototalclock = 0, gotlastpacketclock = 0, smoothratio;
static vec3_t opos[MAXPLAYERS];
static int ohoriz[MAXPLAYERS], ozoom[MAXPLAYERS];
static short oang[MAXPLAYERS];

static vec3_t osprite[MAXSPRITES];

#define MAXINTERPOLATIONS 1024
static int numinterpolations = 0, startofdynamicinterpolations = 0;
static int oldipos[MAXINTERPOLATIONS];
static int bakipos[MAXINTERPOLATIONS];
static int *curipos[MAXINTERPOLATIONS];

// extern int cachecount;

static char playerreadyflag[MAXPLAYERS];

//Miscellaneous variables
static unsigned char packbuf[2048];
static char tempbuf[2048];
static char boardfilename[BMAX_PATH];
static short tempshort[MAXSECTORS];
static short screenpeek = 0, oldmousebstatus = 0;
short brightness = 0;
static short screensize, screensizeflag = 0;
static short neartagsector, neartagwall, neartagsprite;
static int lockclock, neartagdist, neartaghitdist;
extern int pageoffset, ydim16;
static int globhiz, globloz, globhihit, globlohit;

//Over the shoulder mode variables
static int cameradist = -1, cameraang = 0, cameraclock = 0;

//Board animation variables
#define MAXMIRRORS 64
static short mirrorwall[MAXMIRRORS], mirrorsector[MAXMIRRORS], mirrorcnt;
static short floormirrorsector[64], floormirrorcnt;
static short turnspritelist[16], turnspritecnt;
static short warpsectorlist[64], warpsectorcnt;
static short xpanningsectorlist[16], xpanningsectorcnt;
static short ypanningwalllist[64], ypanningwallcnt;
static short floorpanninglist[64], floorpanningcnt;
static short dragsectorlist[16], dragxdir[16], dragydir[16], dragsectorcnt;
static int dragx1[16], dragy1[16], dragx2[16], dragy2[16], dragfloorz[16];
static short swingcnt, swingwall[32][5], swingsector[32];
static short swingangopen[32], swingangclosed[32], swingangopendir[32];
static short swingang[32], swinganginc[32];
static int swingx[32][8], swingy[32][8];
static short revolvesector[4], revolveang[4], revolvecnt;
static int revolvex[4][16], revolvey[4][16];
static int revolvepivotx[4], revolvepivoty[4];
static short subwaytracksector[4][128], subwaynumsectors[4], subwaytrackcnt;
static int subwaystop[4][8], subwaystopcnt[4];
static int subwaytrackx1[4], subwaytracky1[4];
static int subwaytrackx2[4], subwaytracky2[4];
static int subwayx[4], subwaygoalstop[4], subwayvel[4], subwaypausetime[4];
static short waterfountainwall[MAXPLAYERS], waterfountaincnt[MAXPLAYERS];
static short slimesoundcnt[MAXPLAYERS];

//Variables that let you type messages to other player
static char getmessage[162], getmessageleng;
static int getmessagetimeoff;
static char typemessage[162], typemessageleng = 0, typemode = 0;
#if 0
static char scantoasc[128] =
{
    0,0,'1','2','3','4','5','6','7','8','9','0','-','=',0,0,
    'q','w','e','r','t','y','u','i','o','p','[',']',0,0,'a','s',
    'd','f','g','h','j','k','l',';',39,'`',0,92,'z','x','c','v',
    'b','n','m',',','.','/',0,'*',0,32,0,0,0,0,0,0,
    0,0,0,0,0,0,0,'7','8','9','-','4','5','6','+','1',
    '2','3','0','.',0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};
static char scantoascwithshift[128] =
{
    0,0,'!','@','#','$','%','^','&','*','(',')','_','+',0,0,
    'Q','W','E','R','T','Y','U','I','O','P','{','}',0,0,'A','S',
    'D','F','G','H','J','K','L',':',34,'~',0,'|','Z','X','C','V',
    'B','N','M','<','>','?',0,'*',0,32,0,0,0,0,0,0,
    0,0,0,0,0,0,0,'7','8','9','-','4','5','6','+','1',
    '2','3','0','.',0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};
#endif

//These variables are for animating x, y, or z-coordinates of sectors,
//walls, or sprites (They are NOT to be used for changing the [].picnum's)
//See the setanimation(), and getanimategoal() functions for more details.
#define MAXANIMATES 512
static int *animateptr[MAXANIMATES], animategoal[MAXANIMATES];
static int animatevel[MAXANIMATES], animateacc[MAXANIMATES], animatecnt = 0;

#if defined USE_OPENGL
//These parameters are in exact order of sprite structure in BUILD.H
#define spawnsprite(newspriteindex2,x2,y2,z2,cstat2,shade2,pal2,        \
                    clipdist2,xrepeat2,yrepeat2,xoffset2,yoffset2,picnum2,ang2,     \
                    xvel2,yvel2,zvel2,owner2,sectnum2,statnum2,lotag2,hitag2,extra2) \
    {                                                                       \
        spritetype *spr2;                                                   \
        newspriteindex2 = insertsprite(sectnum2,statnum2);                  \
        spr2 = &sprite[newspriteindex2];                                    \
        spr2->x = x2; spr2->y = y2; spr2->z = z2;                           \
        spr2->cstat = cstat2; spr2->shade = shade2;                         \
        spr2->pal = pal2; spr2->clipdist = clipdist2;                       \
        spr2->xrepeat = xrepeat2; spr2->yrepeat = yrepeat2;                 \
        spr2->xoffset = xoffset2; spr2->yoffset = yoffset2;                 \
        spr2->picnum = picnum2; spr2->ang = ang2;                           \
        spr2->xvel = xvel2; spr2->yvel = yvel2; spr2->zvel = zvel2;         \
        spr2->owner = owner2;                                               \
        spr2->lotag = lotag2; spr2->hitag = hitag2; spr2->extra = extra2;   \
        copybuf(&spr2->x,&osprite[newspriteindex2].x,3);                    \
        bitmap_clear(show2dsprite, newspriteindex2);                        \
        if (bitmap_test(show2dsector, sectnum2))                            \
            bitmap_set(show2dsprite, newspriteindex2);                      \
        clearbufbyte(&spriteext[newspriteindex2], sizeof(spriteext_t), 0);  \
    }
#else
#define spawnsprite(newspriteindex2,x2,y2,z2,cstat2,shade2,pal2,        \
                    clipdist2,xrepeat2,yrepeat2,xoffset2,yoffset2,picnum2,ang2,     \
                    xvel2,yvel2,zvel2,owner2,sectnum2,statnum2,lotag2,hitag2,extra2) \
    {                                                                       \
        spritetype *spr2;                                                   \
        newspriteindex2 = insertsprite(sectnum2,statnum2);                  \
        spr2 = &sprite[newspriteindex2];                                    \
        spr2->x = x2; spr2->y = y2; spr2->z = z2;                           \
        spr2->cstat = cstat2; spr2->shade = shade2;                         \
        spr2->pal = pal2; spr2->clipdist = clipdist2;                       \
        spr2->xrepeat = xrepeat2; spr2->yrepeat = yrepeat2;                 \
        spr2->xoffset = xoffset2; spr2->yoffset = yoffset2;                 \
        spr2->picnum = picnum2; spr2->ang = ang2;                           \
        spr2->xvel = xvel2; spr2->yvel = yvel2; spr2->zvel = zvel2;         \
        spr2->owner = owner2;                                               \
        spr2->lotag = lotag2; spr2->hitag = hitag2; spr2->extra = extra2;   \
        copybuf(&spr2->x,&osprite[newspriteindex2].x,3);                    \
        bitmap_clear(show2dsprite, newspriteindex2);                        \
        if (bitmap_test(show2dsector, sectnum2))                            \
            bitmap_set(show2dsprite, newspriteindex2);                      \
    }
#endif

int osdcmd_restartvid(const osdfuncparm_t *parm)
{
    UNREFERENCED_PARAMETER(parm);

    videoResetMode();
    if (videoSetGameMode(fullscreen, xdim, ydim, bpp, upscalefactor))
        LOG_F(ERROR, "restartvid: Reset failed...");

    return OSDCMD_OK;
}

static int osdcmd_vidmode(const osdfuncparm_t *parm)
{
    int newx = xdim, newy = ydim, newbpp = bpp, newfullscreen = fullscreen;

    if (parm->numparms < 1 || parm->numparms > 4) return OSDCMD_SHOWHELP;

    switch (parm->numparms)
    {
    case 1:   // bpp switch
        newbpp = Batol(parm->parms[0]);
        break;
    case 2: // res switch
        newx = Batol(parm->parms[0]);
        newy = Batol(parm->parms[1]);
        break;
    case 3:   // res & bpp switch
    case 4:
        newx = Batol(parm->parms[0]);
        newy = Batol(parm->parms[1]);
        newbpp = Batol(parm->parms[2]);
        if (parm->numparms == 4)
            newfullscreen = (Batol(parm->parms[3]) != 0);
        break;
    }

    if (videoSetGameMode(newfullscreen, newx, newy, newbpp, upscalefactor))
        LOG_F(ERROR, "vidmode: Mode change failed!");
    screensize = xdim+1;
    return OSDCMD_OK;
}

static int osdcmd_map(const osdfuncparm_t *parm)
{
    int i;
    char *dot, namebuf[BMAX_PATH+1];

    if (parm->numparms != 1) return OSDCMD_SHOWHELP;

    strncpy(namebuf, parm->parms[0], BMAX_PATH);
    namebuf[BMAX_PATH] = 0;
    dot = strrchr(namebuf, '.');
    if ((!dot || Bstrcasecmp(dot, ".map")) && strlen(namebuf) <= BMAX_PATH-4)
    {
        strcat(namebuf, ".map");
    }

    prepareboard(namebuf);

    screenpeek = myconnectindex;
    reccnt = 0;
    for (i=connecthead; i>=0; i=connectpoint2[i]) initplayersprite((short)i);

    waitforeverybody();
    totalclock = ototalclock = 0; gotlastpacketclock = 0; nummoves = 0;

    ready2send = 1;
    drawscreen(screenpeek,65536L);

    return OSDCMD_OK;
}

static void Ken_UninitAll(void)
{
    sendlogoff();         //Signing off
    musicoff();
    uninitmultiplayers();
    timerUninit();
    uninitinput();
    uninitsb();
    engineUnInit();
    uninitgroupfile();
}

static void Ken_FatalEngineError(void)
{
    LOG_F(ERROR, "There was a problem initializing the engine: %s", engineerrstr);
}

int32_t app_main(int32_t argc, char const * const * argv)
{
#ifdef _WIN32
#ifndef DEBUGGINGAIDS
    if (!G_CheckCmdSwitch(argc, argv, "-noinstancechecking") && !windowsCheckAlreadyRunning())
    {
#ifdef EDUKE32_STANDALONE
        if (!wm_ynbox(APPNAME, "It looks like " APPNAME " is already running.\n\n"
#else
        if (!wm_ynbox(APPNAME, "It looks like the game is already running.\n\n"
#endif
                      "Are you sure you want to start another copy?"))
            return 3;
    }
#endif
#endif

    Ken_ExtPreInit(argc, argv);

#if defined STARTUP_SETUP_WINDOW
    int cmdsetup = 0;
#endif
    int i, j, k /*, l, fil*/, waitplayers, x1, y1, x2, y2;
    int other, /*packleng, */netparm;

#ifdef __APPLE__
    if (!g_useCwd)
    {
        char cwd[BMAX_PATH];
        char *homedir = Bgethomedir();
        if (homedir)
            Bsnprintf(cwd, sizeof(cwd), "%s/Library/Logs/" APPBASENAME ".log", homedir);
        else
            Bstrcpy(cwd, APPBASENAME ".log");
        OSD_SetLogFile(cwd);
        Xfree(homedir);
    }
    else
#endif
    OSD_SetLogFile(APPBASENAME ".log");

    LOG_F(INFO, "%s %s", AppProperName, s_buildRev);
    PrintBuildInfo();

    if (argc > 1)
    {
        char tempbuf[1024];
        size_t constexpr size = ARRAY_SIZE(tempbuf);
        size_t bytesWritten = Bsnprintf(tempbuf, size, "Application parameters:");
        for (i = 1; i < argc; ++i)
            bytesWritten += Bsnprintf(tempbuf + bytesWritten, size - bytesWritten, " %s", argv[i]);
        LOG_F(INFO, "%s", tempbuf);
    }

    Ken_ExtInit();

    if (enginePreInit())
    {
        wm_msgbox("Build Engine Initialization Error",
                  "There was a problem initializing the engine: %s", engineerrstr);
        exit(1);
    }

    OSD_RegisterFunction("restartvid","restartvid: reinitializes the video mode",osdcmd_restartvid);
    OSD_RegisterFunction("vidmode","vidmode [xdim ydim] [bpp] [fullscreen]: changes the video mode",osdcmd_vidmode);
#ifdef USE_OPENGL
    baselayer_osdcmd_vidmode_func = osdcmd_vidmode;
#endif
    OSD_RegisterFunction("map", "map [filename]: loads a map", osdcmd_map);

    wm_setapptitle(AppProperName);

    Bstrcpy(boardfilename, "NUKELAND.MAP");
    netparm = argc;
    for (i=1; i<argc; i++)
    {
        if ((!Bstrcasecmp("-net",argv[i])) || (!Bstrcasecmp("/net",argv[i]))) { netparm = i+1; break; }
        if (!Bstrcasecmp(argv[i], "-setup"))
        {
#if defined STARTUP_SETUP_WINDOW
                cmdsetup = 1;
#endif
        }
        else
        {
            Bstrcpy(boardfilename, argv[i]);
            if (!Bstrrchr(boardfilename,'.')) Bstrcat(boardfilename,".map");
        }
    }

    if ((i = Ken_loadsetup(setupfilename)) < 0)
        LOG_F(INFO, "Configuration file not found, using defaults.");

    wm_msgbox("Pre-Release Software Warning", "%s is not ready for public use. Proceed with caution!", AppProperName);

#if defined STARTUP_SETUP_WINDOW
    if (i || forcesetup || cmdsetup)
    {
        if (quitevent || !startwin_run()) return -1;
    }
#endif
    Ken_writesetup(setupfilename);

    initgroupfile(G_GrpFile());
    if (engineInit())
    {
        Ken_FatalEngineError();
        return -1;
    }

    Ken_PostStartupWindow();

    initinput();
    if (option[3] != 0) mouseInit();
    timerInit(TIMERINTSPERSECOND);

    //initmultiplayers(argc-netparm,&argv[netparm],option[4],option[5],0);
    if (initmultiplayersparms(argc-netparm,&argv[netparm]))
    {
        LOG_F(INFO, "Waiting for players...");
        while (initmultiplayerscycle())
        {
            handleevents();
            if (quitevent)
            {
                Ken_UninitAll();
                return 0;
            }
        }
    }
    option[4] = (numplayers >= 2);

    artLoadFiles("tiles000.art",1048576);                      //Load artwork
    Ken_LoadVoxels();

    char const * const deffile = G_DefFile();
    uint32_t stime = timerGetTicks();
    if (!loaddefinitionsfile(deffile))
    {
        uint32_t etime = timerGetTicks();
        LOG_F(INFO, "Definitions file '%s' loaded in %d ms.", deffile, etime-stime);
    }

    if (enginePostInit())
    {
        Ken_UninitAll();
        Ken_FatalEngineError();
        return -1;
    }

    OSD_SetFunctions(
        NULL, NULL, NULL, NULL, NULL,
        COMMON_clearbackground,
        BGetTime,
        NULL
        );

    {
        char tempbuf[256];
        snprintf(tempbuf, ARRAY_SIZE(tempbuf), "%s %s", AppProperName, s_buildRev);
        OSD_SetVersion(tempbuf, 10,0);
    }
    OSD_SetParameters(0,2, 0,0, 4,0, 0, 0, 0, 0); // TODO: Add error and red palookup IDs.

    //Here's an example of TRUE ornamented walls
    //The tileCreate should be called right after artLoadFiles
    //Since it resets the tile cache for each call.
    if (tileCreate(SLIME,128,128) == 0)    //If enough memory
    {
        LOG_F(ERROR, "Not enough memory for slime!");
        exit(0);
    }
    if (tileCreate(MAXTILES-1,64,64) != 0)    //If enough memory
    {
        //My face with an explosion written over it
        tileCopySection(KENPICTURE,0,0,64,64,MAXTILES-1,0,0);
        tileCopySection(EXPLOSION,0,0,64,64,MAXTILES-1,0,0);
    }

    initlava();

    palettePostLoadLookups();

    prepareboard(boardfilename);                   //Load board

    initsb(option[1],option[2],digihz[option[7]>>4],((option[7]&4)>0)+1,((option[7]&2)>0)+1,60,option[7]&1);
    //if (Bstrcmp(boardfilename,"klab.map") == 0)
    //   loadsong("klabsong.kdm");
    //else
    loadsong("neatsong.kdm");
    musicon();

    if (option[4] > 0)
    {
        x1 = ((xdim-screensize)>>1);
        x2 = x1+screensize-1;
        y1 = (((ydim-32)-scale(screensize,ydim-32,xdim))>>1);
        y2 = y1 + scale(screensize,ydim-32,xdim)-1;

        drawtilebackground(/*0L,0L,*/ BACKGROUND,8,x1,y1,x2,y2,0);

        sendlogon();

        if (option[4] < 5) waitplayers = 2; else waitplayers = option[4]-3;
        while (numplayers < waitplayers)
        {
            sprintf(tempbuf,"%d of %d players in...",numplayers,waitplayers);
            printext256(68L,84L,31,0,tempbuf,0);
            videoNextPage();

            if (getpacket(&other,packbuf) > 0)
                if (packbuf[0] == 255)
                    keystatus[1] = 1;

            if (handleevents())
            {
                if (quitevent)
                {
                    keystatus[1] = 1;
                    quitevent = 0;
                }
            }

            if (keystatus[1])
            {
                Ken_UninitAll();
                return 0;
            }
        }
        screenpeek = myconnectindex;

        if (numplayers <= 3)
            networkmode = 1;
        else
            networkmode = 0;

        j = 1;
        for (i=connecthead; i>=0; i=connectpoint2[i])
        {
            if (myconnectindex == i) break;
            j++;
        }
        sprintf(getmessage,"Player %d",j);
        if (networkmode == 0)
        {
            if (j == 1) Bstrcat(getmessage," (Master)");
            else Bstrcat(getmessage," (Slave)");
        }
        else
            Bstrcat(getmessage," (Even)");
        getmessageleng = Bstrlen(getmessage);
        getmessagetimeoff = (int32_t) totalclock+120;
    }
    screenpeek = myconnectindex;
    reccnt = 0;
    for (i=connecthead; i>=0; i=connectpoint2[i]) initplayersprite((short)i);

    waitforeverybody();
    totalclock = ototalclock = 0; gotlastpacketclock = 0; nummoves = 0;

    ready2send = 1;
    drawscreen(screenpeek,65536L);

    while (!keystatus[1])       //Main loop starts here
    {
        if (handleevents())
        {
            if (quitevent)
            {
                keystatus[1] = 1;
                quitevent = 0;
            }
        }

        refreshaudio();
        OSD_DispatchQueued();

        // backslash (useful only with KDM)
//      if (keystatus[0x2b]) { keystatus[0x2b] = 0; preparesndbuf(); }

        if ((networkmode == 1) || (myconnectindex != connecthead))
            while (fakemovefifoplc != movefifoend[myconnectindex]) fakedomovethings();

        getpackets();

        if (typemode == 0)           //if normal game keys active
        {
            if ((keystatus[0x2a]&keystatus[0x36]&keystatus[0x13]) > 0)   //Sh.Sh.R (replay)
            {
                keystatus[0x13] = 0;
                playback();
            }

            if (keystatus[0x26]&(keystatus[0x1d]|keystatus[0x9d])) //Load game
            {
                keystatus[0x26] = 0;
                loadgame();
                drawstatusbar(screenpeek);   // Andy did this
            }

            if (keystatus[0x1f]&(keystatus[0x1d]|keystatus[0x9d])) //Save game
            {
                keystatus[0x1f] = 0;
                savegame();
            }
        }

        if ((networkmode == 0) || (option[4] == 0))
        {
            while (movefifoplc != movefifoend[0]) domovethings();
        }
        else
        {
            j = connecthead;
            if (j == myconnectindex) j = connectpoint2[j];
            averagelag[j] = ((averagelag[j]*7+(((movefifoend[myconnectindex]-movefifoend[j]+otherlag[j]+2)&255)<<8))>>3);
            j = max(averagelag[j]>>9,1);
            while (((movefifoend[myconnectindex]-movefifoplc)&(MOVEFIFOSIZ-1)) > j)
            {
                for (i=connecthead; i>=0; i=connectpoint2[i])
                    if (movefifoplc == movefifoend[i]) break;
                if (i >= 0) break;
                if (myconnectindex != connecthead)
                {
                    k = ((movefifoend[myconnectindex]-movefifoend[connecthead]-otherlag[connecthead]+128)&255);
                    if (k > 128+1) ototalclock++;
                    if (k < 128-1) ototalclock--;
                }
                domovethings();
            }
        }
        i = ((int32_t) totalclock-gotlastpacketclock)*(65536/(TIMERINTSPERSECOND/MOVESPERSECOND));

        drawscreen(screenpeek,i);
    }

    Ken_UninitAll();

    return 0;
}

void operatesector(short dasector)
{
    //Door code
    int i, j, /*k, s, nexti, good, cnt,*/ datag;
    int /*dax, day,*/ daz, dax2, day2, /*daz2,*/ centx, centy;
    short startwall, endwall, wallfind[2];

    datag = sector[dasector].lotag;

    startwall = sector[dasector].wallptr;
    endwall = startwall + sector[dasector].wallnum;
    centx = 0L, centy = 0L;
    for (i=startwall; i<endwall; i++)
    {
        centx += wall[i].x;
        centy += wall[i].y;
    }
    centx /= (endwall-startwall);
    centy /= (endwall-startwall);

    //Simple door that moves up  (tag 8 is a combination of tags 6 & 7)
    if ((datag == 6) || (datag == 8))    //If the sector in front is a door
    {
        i = getanimationgoal(&sector[dasector].ceilingz);
        if (i >= 0)      //If door already moving, reverse its direction
        {
            if (datag == 8)
                daz = ((sector[dasector].ceilingz+sector[dasector].floorz)>>1);
            else
                daz = sector[dasector].floorz;

            if (animategoal[i] == daz)
                animategoal[i] = sector[nextsectorneighborz(dasector,sector[dasector].floorz,-1,-1)].ceilingz;
            else
                animategoal[i] = daz;
            animatevel[i] = 0;
        }
        else      //else insert the door's ceiling on the animation list
        {
            if (sector[dasector].ceilingz == sector[dasector].floorz)
                daz = sector[nextsectorneighborz(dasector,sector[dasector].floorz,-1,-1)].ceilingz;
            else
            {
                if (datag == 8)
                    daz = ((sector[dasector].ceilingz+sector[dasector].floorz)>>1);
                else
                    daz = sector[dasector].floorz;
            }
            if ((j = setanimation(&sector[dasector].ceilingz,daz,6L,6L)) >= 0)
                wsayfollow("updowndr.wav",4096L+(krand()&255)-128,256L,&centx,&centy,0);
        }
    }
    //Simple door that moves down
    if ((datag == 7) || (datag == 8)) //If the sector in front's elevator
    {
        i = getanimationgoal(&sector[dasector].floorz);
        if (i >= 0)      //If elevator already moving, reverse its direction
        {
            if (datag == 8)
                daz = ((sector[dasector].ceilingz+sector[dasector].floorz)>>1);
            else
                daz = sector[dasector].ceilingz;

            if (animategoal[i] == daz)
                animategoal[i] = sector[nextsectorneighborz(dasector,sector[dasector].ceilingz,1,1)].floorz;
            else
                animategoal[i] = daz;
            animatevel[i] = 0;
        }
        else      //else insert the elevator's ceiling on the animation list
        {
            if (sector[dasector].floorz == sector[dasector].ceilingz)
                daz = sector[nextsectorneighborz(dasector,sector[dasector].ceilingz,1,1)].floorz;
            else
            {
                if (datag == 8)
                    daz = ((sector[dasector].ceilingz+sector[dasector].floorz)>>1);
                else
                    daz = sector[dasector].ceilingz;
            }
            if ((j = setanimation(&sector[dasector].floorz,daz,6L,6L)) >= 0)
                wsayfollow("updowndr.wav",4096L+(krand()&255)-128,256L,&centx,&centy,0);
        }
    }

    if (datag == 9)   //Smooshy-wall sideways double-door
    {
        //find any points with either same x or same y coordinate
        //  as center (centx, centy) - should be 2 points found.
        wallfind[0] = -1;
        wallfind[1] = -1;
        for (i=startwall; i<endwall; i++)
            if ((wall[i].x == centx) || (wall[i].y == centy))
            {
                if (wallfind[0] == -1)
                    wallfind[0] = i;
                else
                    wallfind[1] = i;
            }

        for (j=0; j<2; j++)
        {
            if ((wall[wallfind[j]].x == centx) && (wall[wallfind[j]].y == centy))
            {
                //find what direction door should open by averaging the
                //  2 neighboring points of wallfind[0] & wallfind[1].
                i = wallfind[j]-1; if (i < startwall) i = endwall-1;
                dax2 = ((wall[i].x+wall[wall[wallfind[j]].point2].x)>>1)-wall[wallfind[j]].x;
                day2 = ((wall[i].y+wall[wall[wallfind[j]].point2].y)>>1)-wall[wallfind[j]].y;
                if (dax2 != 0)
                {
                    dax2 = wall[wall[wall[wallfind[j]].point2].point2].x;
                    dax2 -= wall[wall[wallfind[j]].point2].x;
                    setanimation(&wall[wallfind[j]].x,wall[wallfind[j]].x+dax2,4L,0L);
                    setanimation(&wall[i].x,wall[i].x+dax2,4L,0L);
                    setanimation(&wall[wall[wallfind[j]].point2].x,wall[wall[wallfind[j]].point2].x+dax2,4L,0L);
                }
                else if (day2 != 0)
                {
                    day2 = wall[wall[wall[wallfind[j]].point2].point2].y;
                    day2 -= wall[wall[wallfind[j]].point2].y;
                    setanimation(&wall[wallfind[j]].y,wall[wallfind[j]].y+day2,4L,0L);
                    setanimation(&wall[i].y,wall[i].y+day2,4L,0L);
                    setanimation(&wall[wall[wallfind[j]].point2].y,wall[wall[wallfind[j]].point2].y+day2,4L,0L);
                }
            }
            else
            {
                i = wallfind[j]-1; if (i < startwall) i = endwall-1;
                dax2 = ((wall[i].x+wall[wall[wallfind[j]].point2].x)>>1)-wall[wallfind[j]].x;
                day2 = ((wall[i].y+wall[wall[wallfind[j]].point2].y)>>1)-wall[wallfind[j]].y;
                if (dax2 != 0)
                {
                    setanimation(&wall[wallfind[j]].x,centx,4L,0L);
                    setanimation(&wall[i].x,centx+dax2,4L,0L);
                    setanimation(&wall[wall[wallfind[j]].point2].x,centx+dax2,4L,0L);
                }
                else if (day2 != 0)
                {
                    setanimation(&wall[wallfind[j]].y,centy,4L,0L);
                    setanimation(&wall[i].y,centy+day2,4L,0L);
                    setanimation(&wall[wall[wallfind[j]].point2].y,centy+day2,4L,0L);
                }
            }
        }
        wsayfollow("updowndr.wav",4096L-256L,256L,&centx,&centy,0);
        wsayfollow("updowndr.wav",4096L+256L,256L,&centx,&centy,0);
    }

    if (datag == 13)  //Swinging door
    {
        for (i=0; i<swingcnt; i++)
        {
            if (swingsector[i] == dasector)
            {
                if (swinganginc[i] == 0)
                {
                    if (swingang[i] == swingangclosed[i])
                    {
                        swinganginc[i] = swingangopendir[i];
                        wsayfollow("opendoor.wav",4096L+(krand()&511)-256,256L,&centx,&centy,0);
                    }
                    else
                        swinganginc[i] = -swingangopendir[i];
                }
                else
                    swinganginc[i] = -swinganginc[i];

                for (j=1; j<=3; j++)
                {
                    setinterpolation(&wall[swingwall[i][j]].x);
                    setinterpolation(&wall[swingwall[i][j]].y);
                }
            }
        }
    }

    if (datag == 16)  //True sideways double-sliding door
    {
        //get 2 closest line segments to center (dax, day)
        wallfind[0] = -1;
        wallfind[1] = -1;
        for (i=startwall; i<endwall; i++)
            if (wall[i].lotag == 6)
            {
                if (wallfind[0] == -1)
                    wallfind[0] = i;
                else
                    wallfind[1] = i;
            }

        for (j=0; j<2; j++)
        {
            if ((((wall[wallfind[j]].x+wall[wall[wallfind[j]].point2].x)>>1) == centx) && (((wall[wallfind[j]].y+wall[wall[wallfind[j]].point2].y)>>1) == centy))
            {
                //door was closed
                //find what direction door should open
                i = wallfind[j]-1; if (i < startwall) i = endwall-1;
                dax2 = wall[i].x-wall[wallfind[j]].x;
                day2 = wall[i].y-wall[wallfind[j]].y;
                if (dax2 != 0)
                {
                    dax2 = wall[wall[wall[wall[wallfind[j]].point2].point2].point2].x;
                    dax2 -= wall[wall[wall[wallfind[j]].point2].point2].x;
                    setanimation(&wall[wallfind[j]].x,wall[wallfind[j]].x+dax2,4L,0L);
                    setanimation(&wall[i].x,wall[i].x+dax2,4L,0L);
                    setanimation(&wall[wall[wallfind[j]].point2].x,wall[wall[wallfind[j]].point2].x+dax2,4L,0L);
                    setanimation(&wall[wall[wall[wallfind[j]].point2].point2].x,wall[wall[wall[wallfind[j]].point2].point2].x+dax2,4L,0L);
                }
                else if (day2 != 0)
                {
                    day2 = wall[wall[wall[wall[wallfind[j]].point2].point2].point2].y;
                    day2 -= wall[wall[wall[wallfind[j]].point2].point2].y;
                    setanimation(&wall[wallfind[j]].y,wall[wallfind[j]].y+day2,4L,0L);
                    setanimation(&wall[i].y,wall[i].y+day2,4L,0L);
                    setanimation(&wall[wall[wallfind[j]].point2].y,wall[wall[wallfind[j]].point2].y+day2,4L,0L);
                    setanimation(&wall[wall[wall[wallfind[j]].point2].point2].y,wall[wall[wall[wallfind[j]].point2].point2].y+day2,4L,0L);
                }
            }
            else
            {
                //door was not closed
                i = wallfind[j]-1; if (i < startwall) i = endwall-1;
                dax2 = wall[i].x-wall[wallfind[j]].x;
                day2 = wall[i].y-wall[wallfind[j]].y;
                if (dax2 != 0)
                {
                    setanimation(&wall[wallfind[j]].x,centx,4L,0L);
                    setanimation(&wall[i].x,centx+dax2,4L,0L);
                    setanimation(&wall[wall[wallfind[j]].point2].x,centx,4L,0L);
                    setanimation(&wall[wall[wall[wallfind[j]].point2].point2].x,centx+dax2,4L,0L);
                }
                else if (day2 != 0)
                {
                    setanimation(&wall[wallfind[j]].y,centy,4L,0L);
                    setanimation(&wall[i].y,centy+day2,4L,0L);
                    setanimation(&wall[wall[wallfind[j]].point2].y,centy,4L,0L);
                    setanimation(&wall[wall[wall[wallfind[j]].point2].point2].y,centy+day2,4L,0L);
                }
            }
        }
        wsayfollow("updowndr.wav",4096L-64L,256L,&centx,&centy,0);
        wsayfollow("updowndr.wav",4096L+64L,256L,&centx,&centy,0);
    }
}

void operatesprite(short dasprite)
{
    int datag;

    datag = sprite[dasprite].lotag;

    if (datag == 2)    //A sprite that shoots a bomb
    {
        vec3_t vector = { sprite[dasprite].x,sprite[dasprite].y,sprite[dasprite].z };
        shootgun(dasprite, &vector,
                 sprite[dasprite].ang,100L,sprite[dasprite].sectnum,2);
    }
}

int changehealth(short snum, short deltahealth)
{
    // int dax, day;
    // short good, k, startwall, endwall, s;

    if (health[snum] > 0)
    {
        health[snum] += deltahealth;
        if (health[snum] > 999) health[snum] = 999;

        if (health[snum] <= 0)
        {
            health[snum] = -1;
            wsayfollow("death.wav",4096L+(krand()&127)-64,256L,&pos[snum].x,&pos[snum].y,1);
            sprite[playersprite[snum]].picnum = SKELETON;
        }

        if ((snum == screenpeek) && (screensize <= xdim))
        {
            if (health[snum] > 0)
                sprintf((char *)tempbuf,"Health:%3d",health[snum]);
            else
                sprintf((char *)tempbuf,"YOU STINK!");

            printext((xdim>>1)-(Bstrlen((char *)tempbuf)<<2),ydim-24,(char *)tempbuf,ALPHABET /*,80*/);
        }
    }
    return health[snum] <= 0;       //You were just injured
}

void changenumbombs(short snum, short deltanumbombs)     // Andy did this
{
    numbombs[snum] += deltanumbombs;
    if (numbombs[snum] > 999) numbombs[snum] = 999;
    if (numbombs[snum] <= 0)
    {
        wsayfollow("doh.wav",4096L+(krand()&127)-64,256L,&pos[snum].x,&pos[snum].y,1);
        numbombs[snum] = 0;
    }

    if ((snum == screenpeek) && (screensize <= xdim))
    {
        sprintf((char *)tempbuf,"B:%3d",numbombs[snum]);
        printext(8L,(ydim - 28L),(char *)tempbuf,ALPHABET /*,80*/);
    }
}

void changenummissiles(short snum, short deltanummissiles)     // Andy did this
{
    nummissiles[snum] += deltanummissiles;
    if (nummissiles[snum] > 999) nummissiles[snum] = 999;
    if (nummissiles[snum] <= 0)
    {
        wsayfollow("doh.wav",4096L+(krand()&127)-64,256L,&pos[snum].x,&pos[snum].y,1);
        nummissiles[snum] = 0;
    }

    if ((snum == screenpeek) && (screensize <= xdim))
    {
        sprintf((char *)tempbuf,"M:%3d",nummissiles[snum]);
        printext(8L,(ydim - 20L),(char *)tempbuf,ALPHABET /*,80*/);
    }
}

void changenumgrabbers(short snum, short deltanumgrabbers)     // Andy did this
{
    numgrabbers[snum] += deltanumgrabbers;
    if (numgrabbers[snum] > 999) numgrabbers[snum] = 999;
    if (numgrabbers[snum] <= 0)
    {
        wsayfollow("doh.wav",4096L+(krand()&127)-64,256L,&pos[snum].x,&pos[snum].y,1);
        numgrabbers[snum] = 0;
    }

    if ((snum == screenpeek) && (screensize <= xdim))
    {
        sprintf((char *)tempbuf,"G:%3d",numgrabbers[snum]);
        printext(8L,(ydim - 12L),(char *)tempbuf,ALPHABET /*,80*/);
    }
}

static int ostatusflytime = 0x80000000;
void drawstatusflytime(short snum)     // Andy did this
{
    int nstatusflytime;

    if ((snum == screenpeek) && (screensize <= xdim))
    {
        nstatusflytime = (((flytime[snum] + 119) - lockclock) / 120);
        if (nstatusflytime > 1000) nstatusflytime = 1000;
        else if (nstatusflytime < 0) nstatusflytime = 0;
        if (nstatusflytime != ostatusflytime)
        {
            if (nstatusflytime > 999) sprintf((char *)tempbuf,"FT:BIG");
            else sprintf((char *)tempbuf,"FT:%3d",nstatusflytime);
            printext((xdim - 56L),(ydim - 20L),(char *)tempbuf,ALPHABET /*,80*/);
            ostatusflytime = nstatusflytime;
        }
    }
}

void drawstatusbar(short snum)     // Andy did this
{
    int nstatusflytime;

    if ((snum == screenpeek) && (screensize <= xdim))
    {
        sprintf((char *)tempbuf,"Deaths:%d",deaths[snum]);
        printext((xdim>>1)-(strlen((char *)tempbuf)<<2),ydim-16,(char *)tempbuf,ALPHABET /*,80*/);
        sprintf((char *)tempbuf,"Health:%3d",health[snum]);
        printext((xdim>>1)-(strlen((char *)tempbuf)<<2),ydim-24,(char *)tempbuf,ALPHABET /*,80*/);

        sprintf((char *)tempbuf,"B:%3d",numbombs[snum]);
        printext(8L,(ydim - 28L),(char *)tempbuf,ALPHABET /*,80*/);
        sprintf((char *)tempbuf,"M:%3d",nummissiles[snum]);
        printext(8L,(ydim - 20L),(char *)tempbuf,ALPHABET /*,80*/);
        sprintf((char *)tempbuf,"G:%3d",numgrabbers[snum]);
        printext(8L,(ydim - 12L),(char *)tempbuf,ALPHABET /*,80*/);

        nstatusflytime = (((flytime[snum] + 119) - lockclock) / 120);
        if (nstatusflytime < 0)
        {
            sprintf((char *)tempbuf,"FT:  0");
            ostatusflytime = 0;
        }
        else if (nstatusflytime > 999)
        {
            sprintf((char *)tempbuf,"FT:BIG");
            ostatusflytime = 999;
        }
        else
        {
            sprintf((char *)tempbuf,"FT:%3d",nstatusflytime);
            ostatusflytime = nstatusflytime;
        }
        printext((xdim - 56L),(ydim - 20L),(char *)tempbuf,ALPHABET /*,80*/);
    }
}

static void refreshstatusbar(void)
{
    int32_t i;

    rotatesprite((xdim-320)<<15,(ydim-32)<<16,65536L,0,STATUSBAR,0,0,8+16+64+128,0L,0L,xdim-1L,ydim-1L);
    for (i = ((xdim-320)>>1)-8; i >= 0; i -= 8)
        rotatesprite(i<<16,(ydim-32)<<16,65536L,0,STATUSBARFILL8,0,0,8+16+64+128,0L,0L,xdim-1L,ydim-1L);
    if (i >= -4)
    {
        i += 4;
        rotatesprite(i<<16,(ydim-32)<<16,65536L,0,STATUSBARFILL4,0,0,8+16+64+128,0L,0L,xdim-1L,ydim-1L);
    }
    for (i = ((xdim-320)>>1)+320; i <= xdim-8; i += 8)
        rotatesprite(i<<16,(ydim-32)<<16,65536L,0,STATUSBARFILL8,0,0,8+16+64+128,0L,0L,xdim-1L,ydim-1L);
    if (i <= xdim-4) rotatesprite(i<<16,(ydim-32)<<16,65536L,0,STATUSBARFILL4,0,0,8+16+64+128,0L,0L,xdim-1L,ydim-1L), i += 4;

    drawstatusbar(screenpeek);   // Andy did this
}

void prepareboard(char *daboardfilename)
{
    short startwall, endwall, dasector;
    int i, j, k=0, s, dax, day, /*daz,*/ dax2, day2;

    getmessageleng = 0;
    typemessageleng = 0;

    randomseed = 17L;

    //Clear (do)animation's list
    animatecnt = 0;
    typemode = 0;
    locselectedgun = 0;
    locselectedgun2 = 0;

    if (engineLoadBoard(daboardfilename,0,&pos[0],&ang[0],&cursectnum[0]) == -1)
    {
        musicoff();
        uninitmultiplayers();
        timerUninit();
        uninitinput();
        uninitsb();
        engineUnInit();
        uninitgroupfile();
        printf("Board not found\n");
        exit(0);
    }
    else
    {
        char tempfn[BMAX_PATH + 1], *fp;

        strncpy(tempfn, daboardfilename, BMAX_PATH);
        tempfn[BMAX_PATH] = 0;

        fp = strrchr(tempfn,'.');
        if (fp) *fp = 0;

        if (strlen(tempfn) <= BMAX_PATH-4)
        {
            strcat(tempfn,".mhk");
            engineLoadMHK(tempfn);
        }
    }

    setup3dscreen();

    for (i=0; i<MAXPLAYERS; i++)
    {
        pos[i] = pos[0];
        ang[i] = ang[0];
        cursectnum[i] = cursectnum[0];
        ocursectnum[i] = cursectnum[0];
        horiz[i] = 100;
        lastchaingun[i] = 0;
        health[i] = 100;
        dimensionmode[i] = 3;
        numbombs[i] = 0;
        numgrabbers[i] = 0;
        nummissiles[i] = 0;
        flytime[i] = 0L;
        zoom[i] = 768L;
        deaths[i] = 0L;
        playersprite[i] = -1;
        screensize = xdim;

        opos[i] = pos[0];
        ohoriz[i] = horiz[0];
        ozoom[i] = zoom[0];
        oang[i] = ang[0];
    }

    my = omy = pos[myconnectindex];
    myhoriz = omyhoriz = horiz[myconnectindex];
    myang = omyang = ang[myconnectindex];
    mycursectnum = cursectnum[myconnectindex];
    myzvel = 0;

    movefifoplc = fakemovefifoplc = 0;
    syncvalhead = 0L; othersyncvalhead = 0L;
    syncvaltottail = 0L; syncvaltail = 0L;
    numinterpolations = 0;

    clearbufbyte(&oloc,sizeof(input),0L);
    for (i=0; i<MAXPLAYERS; i++)
    {
        movefifoend[i] = 0;
        clearbufbyte(&ffsync[i],sizeof(input),0L);
        clearbufbyte(&ssync[i],sizeof(input),0L);
        clearbufbyte(&osync[i],sizeof(input),0L);
    }

    //Scan sector tags

    for (i=0; i<MAXPLAYERS; i++)
    {
        waterfountainwall[i] = -1;
        waterfountaincnt[i] = 0;
        slimesoundcnt[i] = 0;
    }
    warpsectorcnt = 0;      //Make a list of warping sectors
    xpanningsectorcnt = 0;  //Make a list of wall x-panning sectors
    floorpanningcnt = 0;    //Make a list of slime sectors
    dragsectorcnt = 0;      //Make a list of moving platforms
    swingcnt = 0;           //Make a list of swinging doors
    revolvecnt = 0;         //Make a list of revolving doors
    subwaytrackcnt = 0;     //Make a list of subways

    floormirrorcnt = 0;
    tilesiz[FLOORMIRROR].x = 0;
    tilesiz[FLOORMIRROR].y = 0;

    for (i=0; i<numsectors; i++)
    {
        switch (sector[i].lotag)
        {
        case 4:
            floorpanninglist[floorpanningcnt++] = i;
            break;
        case 10:
            warpsectorlist[warpsectorcnt++] = i;
            break;
        case 11:
            xpanningsectorlist[xpanningsectorcnt++] = i;
            break;
        case 12:
            dasector = i;
            dax = 0x7fffffff;
            day = 0x7fffffff;
            dax2 = 0x80000000;
            day2 = 0x80000000;
            startwall = sector[i].wallptr;
            endwall = startwall+sector[i].wallnum;
            for (j=startwall; j<endwall; j++)
            {
                if (wall[j].x < dax) dax = wall[j].x;
                if (wall[j].y < day) day = wall[j].y;
                if (wall[j].x > dax2) dax2 = wall[j].x;
                if (wall[j].y > day2) day2 = wall[j].y;
                if (wall[j].lotag == 3) k = j;
            }
            if (wall[k].x == dax) dragxdir[dragsectorcnt] = -16;
            if (wall[k].y == day) dragydir[dragsectorcnt] = -16;
            if (wall[k].x == dax2) dragxdir[dragsectorcnt] = 16;
            if (wall[k].y == day2) dragydir[dragsectorcnt] = 16;

            dasector = wall[startwall].nextsector;
            dragx1[dragsectorcnt] = 0x7fffffff;
            dragy1[dragsectorcnt] = 0x7fffffff;
            dragx2[dragsectorcnt] = 0x80000000;
            dragy2[dragsectorcnt] = 0x80000000;
            startwall = sector[dasector].wallptr;
            endwall = startwall+sector[dasector].wallnum;
            for (j=startwall; j<endwall; j++)
            {
                if (wall[j].x < dragx1[dragsectorcnt]) dragx1[dragsectorcnt] = wall[j].x;
                if (wall[j].y < dragy1[dragsectorcnt]) dragy1[dragsectorcnt] = wall[j].y;
                if (wall[j].x > dragx2[dragsectorcnt]) dragx2[dragsectorcnt] = wall[j].x;
                if (wall[j].y > dragy2[dragsectorcnt]) dragy2[dragsectorcnt] = wall[j].y;

                setinterpolation(&sector[dasector].floorz);
                setinterpolation(&wall[j].x);
                setinterpolation(&wall[j].y);
                auto const nextwall = wall[j].nextwall;
                if ((unsigned)nextwall < MAXWALLS)
                {
                    setinterpolation(&wall[nextwall].x);
                    setinterpolation(&wall[nextwall].y);
                }
            }

            dragx1[dragsectorcnt] += (wall[sector[i].wallptr].x-dax);
            dragy1[dragsectorcnt] += (wall[sector[i].wallptr].y-day);
            dragx2[dragsectorcnt] -= (dax2-wall[sector[i].wallptr].x);
            dragy2[dragsectorcnt] -= (day2-wall[sector[i].wallptr].y);

            dragfloorz[dragsectorcnt] = sector[i].floorz;

            dragsectorlist[dragsectorcnt++] = i;
            break;
        case 13:
            startwall = sector[i].wallptr;
            endwall = startwall+sector[i].wallnum;
            for (j=startwall; j<endwall; j++)
            {
                if (wall[j].lotag == 4)
                {
                    k = wall[wall[wall[wall[j].point2].point2].point2].point2;
                    if ((wall[j].x == wall[k].x) && (wall[j].y == wall[k].y))
                    {
                        //Door opens counterclockwise
                        swingwall[swingcnt][0] = j;
                        swingwall[swingcnt][1] = wall[j].point2;
                        swingwall[swingcnt][2] = wall[wall[j].point2].point2;
                        swingwall[swingcnt][3] = wall[wall[wall[j].point2].point2].point2;
                        swingangopen[swingcnt] = 1536;
                        swingangclosed[swingcnt] = 0;
                        swingangopendir[swingcnt] = -1;
                    }
                    else
                    {
                        //Door opens clockwise
                        swingwall[swingcnt][0] = wall[j].point2;
                        swingwall[swingcnt][1] = j;
                        swingwall[swingcnt][2] = lastwall(j);
                        swingwall[swingcnt][3] = lastwall(swingwall[swingcnt][2]);
                        swingwall[swingcnt][4] = lastwall(swingwall[swingcnt][3]);
                        swingangopen[swingcnt] = 512;
                        swingangclosed[swingcnt] = 0;
                        swingangopendir[swingcnt] = 1;
                    }
                    for (k=0; k<4; k++)
                    {
                        swingx[swingcnt][k] = wall[swingwall[swingcnt][k]].x;
                        swingy[swingcnt][k] = wall[swingwall[swingcnt][k]].y;
                    }

                    swingsector[swingcnt] = i;
                    swingang[swingcnt] = swingangclosed[swingcnt];
                    swinganginc[swingcnt] = 0;
                    swingcnt++;
                }
            }
            break;
        case 14:
            startwall = sector[i].wallptr;
            endwall = startwall+sector[i].wallnum;
            dax = 0L;
            day = 0L;
            for (j=startwall; j<endwall; j++)
            {
                dax += wall[j].x;
                day += wall[j].y;
            }
            revolvepivotx[revolvecnt] = dax / (endwall-startwall);
            revolvepivoty[revolvecnt] = day / (endwall-startwall);

            k = 0;
            for (j=startwall; j<endwall; j++)
            {
                revolvex[revolvecnt][k] = wall[j].x;
                revolvey[revolvecnt][k] = wall[j].y;

                setinterpolation(&wall[j].x);
                setinterpolation(&wall[j].y);
                setinterpolation(&wall[wall[j].nextwall].x);
                setinterpolation(&wall[wall[j].nextwall].y);

                k++;
            }
            revolvesector[revolvecnt] = i;
            revolveang[revolvecnt] = 0;

            revolvecnt++;
            break;
        case 15:
            subwaytracksector[subwaytrackcnt][0] = i;

            subwaystopcnt[subwaytrackcnt] = 0;
            dax = 0x7fffffff;
            day = 0x7fffffff;
            dax2 = 0x80000000;
            day2 = 0x80000000;
            startwall = sector[i].wallptr;
            endwall = startwall+sector[i].wallnum;
            for (j=startwall; j<endwall; j++)
            {
                if (wall[j].x < dax) dax = wall[j].x;
                if (wall[j].y < day) day = wall[j].y;
                if (wall[j].x > dax2) dax2 = wall[j].x;
                if (wall[j].y > day2) day2 = wall[j].y;
            }
            for (j=startwall; j<endwall; j++)
            {
                if (wall[j].lotag == 5)
                {
                    if ((wall[j].x > dax) && (wall[j].y > day) && (wall[j].x < dax2) && (wall[j].y < day2))
                    {
                        subwayx[subwaytrackcnt] = wall[j].x;
                    }
                    else
                    {
                        subwaystop[subwaytrackcnt][subwaystopcnt[subwaytrackcnt]] = wall[j].x;
                        subwaystopcnt[subwaytrackcnt]++;
                    }
                }
            }

            for (j=1; j<subwaystopcnt[subwaytrackcnt]; j++)
                for (k=0; k<j; k++)
                    if (subwaystop[subwaytrackcnt][j] < subwaystop[subwaytrackcnt][k])
                    {
                        s = subwaystop[subwaytrackcnt][j];
                        subwaystop[subwaytrackcnt][j] = subwaystop[subwaytrackcnt][k];
                        subwaystop[subwaytrackcnt][k] = s;
                    }

            subwaygoalstop[subwaytrackcnt] = 0;
            for (j=0; j<subwaystopcnt[subwaytrackcnt]; j++)
                if (klabs(subwaystop[subwaytrackcnt][j]-subwayx[subwaytrackcnt]) < klabs(subwaystop[subwaytrackcnt][subwaygoalstop[subwaytrackcnt]]-subwayx[subwaytrackcnt]))
                    subwaygoalstop[subwaytrackcnt] = j;

            subwaytrackx1[subwaytrackcnt] = dax;
            subwaytracky1[subwaytrackcnt] = day;
            subwaytrackx2[subwaytrackcnt] = dax2;
            subwaytracky2[subwaytrackcnt] = day2;

            subwaynumsectors[subwaytrackcnt] = 1;
            for (j=0; j<numsectors; j++)
                if (j != i)
                {
                    startwall = sector[j].wallptr;
                    if (wall[startwall].x > subwaytrackx1[subwaytrackcnt])
                        if (wall[startwall].y > subwaytracky1[subwaytrackcnt])
                            if (wall[startwall].x < subwaytrackx2[subwaytrackcnt])
                                if (wall[startwall].y < subwaytracky2[subwaytrackcnt])
                                {
                                    if (sector[j].floorz != sector[i].floorz)
                                    {
                                        sector[j].ceilingstat |= 64;
                                        sector[j].floorstat |= 64;
                                    }
                                    subwaytracksector[subwaytrackcnt][subwaynumsectors[subwaytrackcnt]] = j;
                                    subwaynumsectors[subwaytrackcnt]++;
                                }
                }

            subwayvel[subwaytrackcnt] = 64;
            subwaypausetime[subwaytrackcnt] = 720;

            startwall = sector[i].wallptr;
            endwall = startwall+sector[i].wallnum;
            for (k=startwall; k<endwall; k++)
                if (wall[k].x > subwaytrackx1[subwaytrackcnt])
                    if (wall[k].y > subwaytracky1[subwaytrackcnt])
                        if (wall[k].x < subwaytrackx2[subwaytrackcnt])
                            if (wall[k].y < subwaytracky2[subwaytrackcnt])
                                setinterpolation(&wall[k].x);

            for (j=1; j<subwaynumsectors[subwaytrackcnt]; j++)
            {
                dasector = subwaytracksector[subwaytrackcnt][j];

                startwall = sector[dasector].wallptr;
                endwall = startwall+sector[dasector].wallnum;
                for (k=startwall; k<endwall; k++)
                    setinterpolation(&wall[k].x);

                for (k=headspritesect[dasector]; k>=0; k=nextspritesect[k])
                    if (statrate[sprite[k].statnum] < 0)
                        setinterpolation(&sprite[k].x);
            }


            subwaytrackcnt++;
            break;
        }
        if (sector[i].floorpicnum == FLOORMIRROR)
            floormirrorsector[mirrorcnt++] = i;
        //if (sector[i].ceilingpicnum == FLOORMIRROR) floormirrorsector[mirrorcnt++] = i; //SOS
    }

    //Scan wall tags

    mirrorcnt = 0;
    tilesiz[MIRROR].x = 0;
    tilesiz[MIRROR].y = 0;
    for (i=0; i<MAXMIRRORS; i++)
    {
        tilesiz[i+MIRRORLABEL].x = 0;
        tilesiz[i+MIRRORLABEL].y = 0;
    }

    ypanningwallcnt = 0;
    for (i=0; i<numwalls; i++)
    {
        if (wall[i].lotag == 1) ypanningwalllist[ypanningwallcnt++] = i;
        s = wall[i].nextsector;
        if ((s >= 0) && (wall[i].overpicnum == MIRROR) && (wall[i].cstat&32))
        {
            if ((sector[s].floorstat&1) == 0)
            {
                wall[i].overpicnum = MIRRORLABEL+mirrorcnt;
                sector[s].ceilingpicnum = MIRRORLABEL+mirrorcnt;
                sector[s].floorpicnum = MIRRORLABEL+mirrorcnt;
                sector[s].floorstat |= 1;
                mirrorwall[mirrorcnt] = i;
                mirrorsector[mirrorcnt] = s;
                mirrorcnt++;
            }
            else
                wall[i].overpicnum = sector[s].ceilingpicnum;
        }
    }

    //Invalidate textures in sector behind mirror
    for (i=0; i<mirrorcnt; i++)
    {
        k = mirrorsector[i];
        startwall = sector[k].wallptr;
        endwall = startwall + sector[k].wallnum;
        for (j=startwall; j<endwall; j++)
        {
            wall[j].picnum = MIRROR;
            wall[j].overpicnum = MIRROR;
        }
    }

    //Scan sprite tags&picnum's

    turnspritecnt = 0;
    for (i=0; i<MAXSPRITES; i++)
    {
        if (sprite[i].lotag == 3) turnspritelist[turnspritecnt++] = i;

        if (sprite[i].statnum < MAXSTATUS)    //That is, if sprite exists
            switch (sprite[i].picnum)
            {
            case BROWNMONSTER:              //All cases here put the sprite
                if ((sprite[i].cstat&128) == 0)
                {
                    sprite[i].z -= ((tilesiz[sprite[i].picnum].y*sprite[i].yrepeat)<<1);
                    sprite[i].cstat |= 128;
                }
                sprite[i].extra = sprite[i].ang;
                sprite[i].clipdist = mulscale7(sprite[i].xrepeat,tilesiz[sprite[i].picnum].x);
                if (sprite[i].statnum != 1) changespritestat(i,2);   //on waiting for you (list 2)
                sprite[i].lotag = mulscale5(sprite[i].xrepeat,sprite[i].yrepeat);
                sprite[i].cstat |= 0x101;    //Set the hitscan sensitivity bit
                break;
            case AL:
                sprite[i].cstat |= 0x101;    //Set the hitscan sensitivity bit
                sprite[i].lotag = 0x60;
                changespritestat(i,0);
                break;
            case EVILAL:
                sprite[i].cstat |= 0x101;    //Set the hitscan sensitivity bit
                sprite[i].lotag = 0x60;
                changespritestat(i,10);
                break;
            }
    }

    for (i=MAXSPRITES-1; i>=0; i--) copybuf(&sprite[i].x,&osprite[i].x,3);

    searchmap(cursectnum[connecthead]);

    lockclock = 0;
    ototalclock = 0;
    gotlastpacketclock = 0;

    screensize = xdim;
    dax = ((xdim-screensize)>>1);
    dax2 = dax+screensize-1;
    day = (((ydim-32)-scale(screensize,ydim-32,xdim))>>1);
    day2 = day + scale(screensize,ydim-32,xdim)-1;
    videoSetViewableArea(dax,day,dax2,day2);

    startofdynamicinterpolations = numinterpolations;

#if 0
    for (i=connecthead; i>=0; i=connectpoint2[i]) myminlag[i] = 0;
    otherminlag = mymaxlag = 0;
#endif
}

void checktouchsprite(short snum, short sectnum)
{
    int i, nexti;

    if ((sectnum < 0) || (sectnum >= numsectors)) return;

    for (i=headspritesect[sectnum]; i>=0; i=nexti)
    {
        nexti = nextspritesect[i];
        if (sprite[i].cstat&0x8000) continue;
        if ((klabs(pos[snum].x-sprite[i].x)+klabs(pos[snum].y-sprite[i].y) < 512) && (klabs((pos[snum].z>>8)-((sprite[i].z>>8)-(tilesiz[sprite[i].picnum].y>>1))) <= 40))
        {
            switch (sprite[i].picnum)
            {
            case COIN:
                wsayfollow("getstuff.wav",4096L+(krand()&127)-64,192L,&sprite[i].x,&sprite[i].y,0);
                changehealth(snum,5);
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*60;
                    changespritestat((short)i,11);
                }
                break;
            case DIAMONDS:
                wsayfollow("getstuff.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                changehealth(snum,15);
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*120;
                    changespritestat((short)i,11);
                }
                break;
            case COINSTACK:
                wsayfollow("getstuff.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                changehealth(snum,25);
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*180;
                    changespritestat((short)i,11);
                }
                break;
            case GIFTBOX:
                wsayfollow("getstuff.wav",4096L+(krand()&127)+256-mulscale4(sprite[i].xrepeat,sprite[i].yrepeat),208L,&sprite[i].x,&sprite[i].y,0);
                changehealth(snum,max(mulscale8(sprite[i].xrepeat,sprite[i].yrepeat),1));
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 90*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            case CANNON:
                wsayfollow("getstuff.wav",3584L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                if (snum == myconnectindex) keystatus[4] = 1;
                changenumbombs(snum,((sprite[i].xrepeat+sprite[i].yrepeat)>>1));
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 60*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            case LAUNCHER:
                wsayfollow("getstuff.wav",3584L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                if (snum == myconnectindex) keystatus[5] = 1;
                changenummissiles(snum,((sprite[i].xrepeat+sprite[i].yrepeat)>>1));
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 90*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            case GRABCANNON:
                wsayfollow("getstuff.wav",3584L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                if (snum == myconnectindex) keystatus[6] = 1;
                changenumgrabbers(snum,((sprite[i].xrepeat+sprite[i].yrepeat)>>1));
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            case AIRPLANE:
                wsayfollow("getstuff.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                if (flytime[snum] < lockclock) flytime[snum] = lockclock;
                flytime[snum] += 60*(sprite[i].xrepeat+sprite[i].yrepeat);
                drawstatusflytime(snum);
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            }
        }
    }
}

void checkgrabbertouchsprite(short snum, short sectnum)   // Andy did this
{
    int i, nexti;
    short onum;

    if ((sectnum < 0) || (sectnum >= numsectors)) return;
    onum = (sprite[snum].owner & (MAXSPRITES - 1));

    for (i=headspritesect[sectnum]; i>=0; i=nexti)
    {
        nexti = nextspritesect[i];
        if (sprite[i].cstat&0x8000) continue;
        if ((klabs(sprite[snum].x-sprite[i].x)+klabs(sprite[snum].y-sprite[i].y) < 512) && (klabs((sprite[snum].z>>8)-((sprite[i].z>>8)-(tilesiz[sprite[i].picnum].y>>1))) <= 40))
        {
            switch (sprite[i].picnum)
            {
            case COIN:
                wsayfollow("getstuff.wav",4096L+(krand()&127)-64,192L,&sprite[i].x,&sprite[i].y,0);
                changehealth(onum,5);
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*60;
                    changespritestat((short)i,11);
                }
                break;
            case DIAMONDS:
                wsayfollow("getstuff.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                changehealth(onum,15);
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*120;
                    changespritestat((short)i,11);
                }
                break;
            case COINSTACK:
                wsayfollow("getstuff.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                changehealth(onum,25);
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*180;
                    changespritestat((short)i,11);
                }
                break;
            case GIFTBOX:
                wsayfollow("getstuff.wav",4096L+(krand()&127)+256-mulscale4(sprite[i].xrepeat,sprite[i].yrepeat),208L,&sprite[i].x,&sprite[i].y,0);
                changehealth(onum,max(mulscale8(sprite[i].xrepeat,sprite[i].yrepeat),1));
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 90*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            case CANNON:
                wsayfollow("getstuff.wav",3584L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                if (onum == myconnectindex) keystatus[4] = 1;
                changenumbombs(onum,((sprite[i].xrepeat+sprite[i].yrepeat)>>1));
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 60*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            case LAUNCHER:
                wsayfollow("getstuff.wav",3584L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                if (onum == myconnectindex) keystatus[5] = 1;
                changenummissiles(onum,((sprite[i].xrepeat+sprite[i].yrepeat)>>1));
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 90*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            case GRABCANNON:
                wsayfollow("getstuff.wav",3584L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                if (onum == myconnectindex) keystatus[6] = 1;
                changenumgrabbers(onum,((sprite[i].xrepeat+sprite[i].yrepeat)>>1));
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            case AIRPLANE:
                wsayfollow("getstuff.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                if (flytime[snum] < lockclock) flytime[snum] = lockclock;
                flytime[onum] += 60*(sprite[i].xrepeat+sprite[i].yrepeat);
                drawstatusflytime(onum);
                if (sprite[i].statnum == 12) deletesprite((short)i);
                else
                {
                    sprite[i].cstat |= 0x8000;
                    sprite[i].extra = 120*(sprite[i].xrepeat+sprite[i].yrepeat);
                    changespritestat((short)i,11);
                }
                break;
            }
        }
    }
}

void shootgun(short snum, const vec3_t *vector,
              short daang, int dahoriz, short dasectnum, char guntype)
{
    short daang2;
    int /*i,*/ j, daz2;
    hitdata_t hitinfo;

    switch (guntype)
    {
    case 0:    //Shoot chain gun
        daang2 = ((daang + (krand()&31)-16)&2047);
        daz2 = ((100-dahoriz)*2000) + ((krand()-32768)>>1);

        hitscan(vector,dasectnum,                   //Start position
                sintable[(daang2+512)&2047],            //X vector of 3D ang
                sintable[daang2&2047],                  //Y vector of 3D ang
                daz2,                                   //Z vector of 3D ang
                &hitinfo,CLIPMASK1);

        if ((unsigned)hitinfo.wall < MAXWALLS && wall[hitinfo.wall].picnum == KENPICTURE)
        {
            if (waloff[MAXTILES-1] != 0) wall[hitinfo.wall].picnum = MAXTILES-1;
            wsayfollow("hello.wav",4096L+(krand()&127)-64,256L,&wall[hitinfo.wall].x,&wall[hitinfo.wall].y,0);
        }
        else if (((hitinfo.wall < 0) && (hitinfo.sprite < 0) && (hitinfo.z >= vector->z) && ((sector[hitinfo.sect].floorpicnum == SLIME) || (sector[hitinfo.sect].floorpicnum == FLOORMIRROR))) || ((hitinfo.wall >= 0) && (wall[hitinfo.wall].picnum == SLIME)))
        {
            //If you shoot slime, make a splash
            wsayfollow("splash.wav",4096L+(krand()&511)-256,256L,&hitinfo.x,&hitinfo.y,0);
            spawnsprite(j,hitinfo.x,hitinfo.y,hitinfo.z,2,0,0,32,64,64,0,0,SPLASH,daang,
                        0,0,0,snum+4096,hitinfo.sect,4,63,0,0); //63=time left for splash
        }
        else
        {
            wsayfollow("shoot.wav",4096L+(krand()&127)-64,256L,&hitinfo.x,&hitinfo.y,0);

            if ((hitinfo.sprite >= 0) && (sprite[hitinfo.sprite].statnum < MAXSTATUS))
                switch (sprite[hitinfo.sprite].picnum)
                {
                case BROWNMONSTER:
                    if (sprite[hitinfo.sprite].lotag > 0) sprite[hitinfo.sprite].lotag -= 10;
                    if (sprite[hitinfo.sprite].lotag > 0)
                    {
                        wsayfollow("hurt.wav",4096L+(krand()&511)-256,256L,&hitinfo.x,&hitinfo.y,0);
                        if (sprite[hitinfo.sprite].lotag <= 25)
                            sprite[hitinfo.sprite].cstat |= 2;
                    }
                    else
                    {
                        wsayfollow("mondie.wav",4096L+(krand()&127)-64,256L,&hitinfo.x,&hitinfo.y,0);
                        sprite[hitinfo.sprite].z += ((tilesiz[sprite[hitinfo.sprite].picnum].y*sprite[hitinfo.sprite].yrepeat)<<1);
                        sprite[hitinfo.sprite].picnum = GIFTBOX;
                        sprite[hitinfo.sprite].cstat &= ~0x83;    //Should not clip, foot-z
                        changespritestat(hitinfo.sprite,12);

                        spawnsprite(j,hitinfo.x,hitinfo.y,hitinfo.z+(32<<8),0,-4,0,32,64,64,
                                    0,0,EXPLOSION,daang,0,0,0,snum+4096,
                                    hitinfo.sect,5,31,0,0);
                    }
                    break;
                case EVILAL:
                    wsayfollow("blowup.wav",4096L+(krand()&127)-64,256L,&hitinfo.x,&hitinfo.y,0);
                    sprite[hitinfo.sprite].picnum = EVILALGRAVE;
                    sprite[hitinfo.sprite].cstat = 0;
                    sprite[hitinfo.sprite].xvel = (krand()&255)-128;
                    sprite[hitinfo.sprite].yvel = (krand()&255)-128;
                    sprite[hitinfo.sprite].zvel = (krand()&4095)-3072;
                    changespritestat(hitinfo.sprite,9);

                    spawnsprite(j,hitinfo.x,hitinfo.y,hitinfo.z+(32<<8),0,-4,0,32,64,64,0,
                                0,EXPLOSION,daang,0,0,0,snum+4096,hitinfo.sect,5,31,0,0);
                    //31=time left for explosion

                    break;
                case PLAYER:
                    for (j=connecthead; j>=0; j=connectpoint2[j])
                        if (playersprite[j] == hitinfo.sprite)
                        {
                            wsayfollow("ouch.wav",4096L+(krand()&127)-64,256L,&hitinfo.x,&hitinfo.y,0);
                            changehealth(j,-10);
                            break;
                        }
                    break;
                }

            spawnsprite(j,hitinfo.x,hitinfo.y,hitinfo.z+(8<<8),2,-4,0,32,16,16,0,0,
                        EXPLOSION,daang,0,0,0,snum+4096,hitinfo.sect,3,63,0,0);

            //Sprite starts out with center exactly on wall.
            //This moves it back enough to see it at all angles.
            movesprite((short)j,-(((int)sintable[(512+daang)&2047]*TICSPERFRAME)<<4),-(((int)sintable[daang]*TICSPERFRAME)<<4),0L,4L<<8,4L<<8,CLIPMASK1);
        }
        break;
    case 1:    //Shoot silver sphere bullet
        spawnsprite(j,vector->x,vector->y,vector->z,1+128,0,0,16,64,64,0,0,BULLET,daang,
                    sintable[(daang+512)&2047]>>5,sintable[daang&2047]>>5,
                    (100-dahoriz)<<6,snum+4096,dasectnum,6,0,0,0);
        wsayfollow("shoot2.wav",4096L+(krand()&127)-64,128L,&sprite[j].x,&sprite[j].y,1);
        break;
    case 2:    //Shoot bomb
        spawnsprite(j,vector->x,vector->y,vector->z,128,0,0,12,16,16,0,0,BOMB,daang,
                    sintable[(daang+512)&2047]*5>>8,sintable[daang&2047]*5>>8,
                    (80-dahoriz)<<6,snum+4096,dasectnum,6,0,0,0);
        wsayfollow("shoot3.wav",4096L+(krand()&127)-64,192L,&sprite[j].x,&sprite[j].y,1);
        break;
    case 3:    //Shoot missile (Andy did this)
        spawnsprite(j,vector->x,vector->y,vector->z,1+128,0,0,16,32,32,0,0,MISSILE,daang,
                    sintable[(daang+512)&2047]>>4,sintable[daang&2047]>>4,
                    (100-dahoriz)<<7,snum+4096,dasectnum,6,0,0,0);
        wsayfollow("shoot3.wav",4096L+(krand()&127)-64,192L,&sprite[j].x,&sprite[j].y,1);
        break;
    case 4:    //Shoot grabber (Andy did this)
        spawnsprite(j,vector->x,vector->y,vector->z,1+128,0,0,16,64,64,0,0,GRABBER,daang,
                    sintable[(daang+512)&2047]>>5,sintable[daang&2047]>>5,
                    (100-dahoriz)<<6,snum+4096,dasectnum,6,0,0,0);
        wsayfollow("shoot4.wav",4096L+(krand()&127)-64,128L,&sprite[j].x,&sprite[j].y,1);
        break;
    }
}

void analyzesprites(int dax, int day)
{
    int i, j=0, k;
    vec3_t *ospr;
    tspriteptr_t tspr;

    //This function is called between drawrooms() and renderDrawMasks()
    //It has a list of possible sprites that may be drawn on this frame

    for (i=0,tspr=&tsprite[0]; i<spritesortcnt; i++,tspr++)
    {
        Bassert((unsigned)tspr->owner < MAXSPRITES);

        if (usevoxels && videoGetRenderMode() != REND_POLYMER && !(spriteext[tspr->owner].flags&SPREXT_NOTMD))
            switch (tspr->picnum)
            {
            case PLAYER:
                //   //Get which of the 8 angles of the sprite to draw (0-7)
                //   //k ranges from 0-7
                //k = getangle(tspr->x-dax,tspr->y-day);
                //k = (((tspr->ang+3072+128-k)&2047)>>8)&7;
                //   //This guy has only 5 pictures for 8 angles (3 are x-flipped)
                //if (k <= 4)
                //{
                //   tspr->picnum += (k<<2);
                //   tspr->cstat &= ~4;   //clear x-flipping bit
                //}
                //else
                //{
                //   tspr->picnum += ((8-k)<<2);
                //   tspr->cstat |= 4;    //set x-flipping bit
                //}

                if ((tspr->cstat&2) == 0)
                {
                    if (voxid_PLAYER == -1)
                        break;

                    tspr->clipdist |= TSPR_FLAGS_SLAB;
                    tspr->cstat &= ~CSTAT_SPRITE_ALIGNMENT;
                    tspr->picnum = voxid_PLAYER;

                    auto const voxptr = (int32_t const *)voxoff[voxid_PLAYER][0];
                    tspr->xrepeat = scale(tspr->xrepeat,56,voxptr[2]);
                    tspr->yrepeat = scale(tspr->yrepeat,56,voxptr[2]);
                    tspr->shade -= 6;
                }
                break;
            case BROWNMONSTER:
                if (voxid_BROWNMONSTER == -1)
                    break;

                tspr->clipdist |= TSPR_FLAGS_SLAB;
                tspr->cstat &= ~CSTAT_SPRITE_ALIGNMENT;
                tspr->picnum = voxid_BROWNMONSTER;
                break;
            }

        k = statrate[tspr->statnum];
        if (k >= 0)  //Interpolate moving sprite
        {
            ospr = &osprite[tspr->owner];
            switch (k)
            {
            case 0: j = smoothratio; break;
            case 1: j = (smoothratio>>1)+(((nummoves-tspr->owner)&1)<<15); break;
            case 3: j = (smoothratio>>2)+(((nummoves-tspr->owner)&3)<<14); break;
            case 7: j = (smoothratio>>3)+(((nummoves-tspr->owner)&7)<<13); break;
            case 15: j = (smoothratio>>4)+(((nummoves-tspr->owner)&15)<<12); break;
            }
            k = tspr->x-ospr->x; tspr->x = ospr->x;
            if (k != 0) tspr->x += mulscale16(k,j);
            k = tspr->y-ospr->y; tspr->y = ospr->y;
            if (k != 0) tspr->y += mulscale16(k,j);
            k = tspr->z-ospr->z; tspr->z = ospr->z;
            if (k != 0) tspr->z += mulscale16(k,j);
        }

        //Don't allow close explosion sprites to be transluscent
        k = tspr->statnum;
        if ((k == 3) || (k == 4) || (k == 5) || (k == 7))
            if (klabs(dax-tspr->x) < 256)
                if (klabs(day-tspr->y) < 256)
                    tspr->cstat &= ~2;

        tspr->shade += 6;
        if (sector[tspr->sectnum].ceilingstat&1)
            tspr->shade += sector[tspr->sectnum].ceilingshade;
        else
            tspr->shade += sector[tspr->sectnum].floorshade;
    }
}

void tagcode(void)
{
    int i, /*nexti,*/ j, k, l, s, /*daz, dax2, day2,*/ cnt, good;
    short startwall, endwall, dasector, p, oldang;

    for (p=connecthead; p>=0; p=connectpoint2[p])
    {
        if (sector[cursectnum[p]].lotag == 1)
        {
            activatehitag(sector[cursectnum[p]].hitag);
            sector[cursectnum[p]].lotag = 0;
            sector[cursectnum[p]].hitag = 0;
        }
        if ((sector[cursectnum[p]].lotag == 2) && (cursectnum[p] != ocursectnum[p]))
            activatehitag(sector[cursectnum[p]].hitag);
    }

    for (i=0; i<warpsectorcnt; i++)
    {
        dasector = warpsectorlist[i];
        j = ((lockclock&127)>>2);
        if (j >= 16) j = 31-j;
        {
            sector[dasector].ceilingshade = j;
            sector[dasector].floorshade = j;
            startwall = sector[dasector].wallptr;
            endwall = startwall+sector[dasector].wallnum;
            for (s=startwall; s<endwall; s++)
                wall[s].shade = j;
        }
    }

    for (p=connecthead; p>=0; p=connectpoint2[p])
        if (sector[cursectnum[p]].lotag == 10)  //warp sector
        {
            if (cursectnum[p] != ocursectnum[p])
            {
                warpsprite(playersprite[p]);
                pos[p].x = sprite[playersprite[p]].x;
                pos[p].y = sprite[playersprite[p]].y;
                pos[p].z = sprite[playersprite[p]].z;
                ang[p] = sprite[playersprite[p]].ang;
                cursectnum[p] = sprite[playersprite[p]].sectnum;

                sprite[playersprite[p]].z += EYEHEIGHT;

                //warp(&pos[p].x,&pos[p].y,&pos[p].z,&ang[p],&cursectnum[p]);
                //Update sprite representation of player
                //setsprite_eyeheight(playersprite[p],&pos[p]);
                //sprite[playersprite[p]].ang = ang[p];
            }
        }

    for (i=0; i<xpanningsectorcnt; i++) //animate wall x-panning sectors
    {
        dasector = xpanningsectorlist[i];

        startwall = sector[dasector].wallptr;
        endwall = startwall+sector[dasector].wallnum;
        for (s=startwall; s<endwall; s++)
            wall[s].xpanning = ((lockclock>>2)&255);
    }

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

    for (i=0; i<turnspritecnt; i++)
    {
        sprite[turnspritelist[i]].ang += (TICSPERFRAME<<2);
        sprite[turnspritelist[i]].ang &= 2047;
    }

    for (i=0; i<floorpanningcnt; i++) //animate floor of slime sectors
    {
        sector[floorpanninglist[i]].floorxpanning = ((lockclock>>2)&255);
        sector[floorpanninglist[i]].floorypanning = ((lockclock>>2)&255);
    }

    for (i=0; i<dragsectorcnt; i++)
    {
        dasector = dragsectorlist[i];

        startwall = sector[dasector].wallptr;
        endwall = startwall+sector[dasector].wallnum;

        if (wall[startwall].x+dragxdir[i] < dragx1[i]) dragxdir[i] = 16;
        if (wall[startwall].y+dragydir[i] < dragy1[i]) dragydir[i] = 16;
        if (wall[startwall].x+dragxdir[i] > dragx2[i]) dragxdir[i] = -16;
        if (wall[startwall].y+dragydir[i] > dragy2[i]) dragydir[i] = -16;

        for (j=startwall; j<endwall; j++)
            dragpoint(j,wall[j].x+dragxdir[i],wall[j].y+dragydir[i],0);
        j = sector[dasector].floorz;
        sector[dasector].floorz = dragfloorz[i]+(sintable[(lockclock<<4)&2047]>>3);

        for (p=connecthead; p>=0; p=connectpoint2[p])
            if (cursectnum[p] == dasector)
            {
                pos[p].x += dragxdir[i];
                pos[p].y += dragydir[i];
                if (p == myconnectindex)
                { my.x += dragxdir[i]; my.y += dragydir[i]; }
                //pos[p].z += (sector[dasector].floorz-j);

                //Update sprite representation of player
                setsprite_eyeheight(playersprite[p],&pos[p]);
                sprite[playersprite[p]].ang = ang[p];
            }
    }

    for (i=0; i<swingcnt; i++)
    {
        if (swinganginc[i] != 0)
        {
            oldang = swingang[i];
            for (j=0; j<(TICSPERFRAME<<2); j++)
            {
                swingang[i] = ((swingang[i]+swinganginc[i])&2047);
                if (swingang[i] == swingangclosed[i])
                {
                    wsayfollow("closdoor.wav",4096L+(krand()&511)-256,256L,&swingx[i][0],&swingy[i][0],0);
                    swinganginc[i] = 0;
                }
                if (swingang[i] == swingangopen[i]) swinganginc[i] = 0;
            }
            for (k=1; k<=3; k++)
            {
                vec2_t const pivot = { swingx[i][0], swingy[i][0] };
                vec2_t const p = { swingx[i][k], swingy[i][k] };
                rotatepoint(pivot, p, swingang[i], &wall[swingwall[i][k]].xy);
            }

            if (swinganginc[i] != 0)
            {
                for (p=connecthead; p>=0; p=connectpoint2[p])
                    if ((cursectnum[p] == swingsector[i]) || (testneighborsectors(cursectnum[p],swingsector[i]) == 1))
                    {
                        cnt = 256;
                        do
                        {
                            good = 1;

                            //swingangopendir is -1 if forwards, 1 is backwards
                            l = (swingangopendir[i] > 0);
                            for (k=l+3; k>=l; k--)
                                if (clipinsidebox(pos[p].xy,swingwall[i][k],128L) != 0)
                                {
                                    good = 0;
                                    break;
                                }
                            if (good == 0)
                            {
                                if (cnt == 256)
                                {
                                    swinganginc[i] = -swinganginc[i];
                                    swingang[i] = oldang;
                                }
                                else
                                {
                                    swingang[i] = ((swingang[i]-swinganginc[i])&2047);
                                }
                                for (k=1; k<=3; k++)
                                {
                                    vec2_t const pivot = { swingx[i][0], swingy[i][0] };
                                    vec2_t const p = { swingx[i][k], swingy[i][k] };
                                    rotatepoint(pivot, p, swingang[i], &wall[swingwall[i][k]].xy);
                                }
                                if (swingang[i] == swingangclosed[i])
                                {
                                    wsayfollow("closdoor.wav",4096L+(krand()&511)-256,256L,&swingx[i][0],&swingy[i][0],0);
                                    swinganginc[i] = 0;
                                    break;
                                }
                                if (swingang[i] == swingangopen[i])
                                {
                                    swinganginc[i] = 0;
                                    break;
                                }
                                cnt--;
                            }
                        }
                        while ((good == 0) && (cnt > 0));
                    }
            }
        }
        if (swinganginc[i] == 0)
            for (j=1; j<=3; j++)
            {
                stopinterpolation(&wall[swingwall[i][j]].x);
                stopinterpolation(&wall[swingwall[i][j]].y);
            }
    }

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

        revolveang[i] = ((revolveang[i]-(TICSPERFRAME<<2))&2047);
        for (k=startwall; k<endwall; k++)
        {
            vec2_t const pivot = { revolvepivotx[i], revolvepivoty[i] };
            vec2_t const p = { revolvex[i][k-startwall], revolvey[i][k-startwall] };
            vec2_t daxy;
            rotatepoint(pivot, p, revolveang[i], &daxy);
            dragpoint(k,daxy.x,daxy.y,0);
        }
    }

    for (i=0; i<subwaytrackcnt; i++)
    {
        if ((subwayvel[i] < -2) || (subwayvel[i] > 2))
        {
            dasector = subwaytracksector[i][0];
            startwall = sector[dasector].wallptr;
            endwall = startwall+sector[dasector].wallnum;
            for (k=startwall; k<endwall; k++)
                if (wall[k].x > subwaytrackx1[i])
                    if (wall[k].y > subwaytracky1[i])
                        if (wall[k].x < subwaytrackx2[i])
                            if (wall[k].y < subwaytracky2[i])
                                wall[k].x += subwayvel[i];

            for (j=1; j<subwaynumsectors[i]; j++)
            {
                dasector = subwaytracksector[i][j];

                startwall = sector[dasector].wallptr;
                endwall = startwall+sector[dasector].wallnum;
                for (k=startwall; k<endwall; k++)
                    wall[k].x += subwayvel[i];

                for (s=headspritesect[dasector]; s>=0; s=nextspritesect[s])
                    sprite[s].x += subwayvel[i];
            }

            for (p=connecthead; p>=0; p=connectpoint2[p])
                if (cursectnum[p] != subwaytracksector[i][0])
                    if (sector[cursectnum[p]].floorz != sector[subwaytracksector[i][0]].floorz)
                        if (pos[p].x > subwaytrackx1[i])
                            if (pos[p].y > subwaytracky1[i])
                                if (pos[p].x < subwaytrackx2[i])
                                    if (pos[p].y < subwaytracky2[i])
                                    {
                                        pos[p].x += subwayvel[i];
                                        if (p == myconnectindex)
                                        { my.x += subwayvel[i]; }

                                        //Update sprite representation of player
                                        setsprite_eyeheight(playersprite[p],&pos[p]);
                                        sprite[playersprite[p]].ang = ang[p];
                                    }

            subwayx[i] += subwayvel[i];
        }

        j = subwayvel[i];
        k = subwaystop[i][subwaygoalstop[i]] - subwayx[i];
        if (k > 0)
        {
            if (k > 4096)
            {
                if (subwayvel[i] < 256) subwayvel[i]++;
            }
            else
                subwayvel[i] = (k>>4)+1;
        }
        else if (k < 0)
        {
            if (k < -4096)
            {
                if (subwayvel[i] > -256) subwayvel[i]--;
            }
            else
                subwayvel[i] = (k>>4)-1;
        }
        if ((j < 0) && (subwayvel[i] >= 0)) subwayvel[i] = -1;
        if ((j > 0) && (subwayvel[i] <= 0)) subwayvel[i] = 1;

        if ((subwayvel[i] <= 2) && (subwayvel[i] >= -2) && (klabs(k) < 2048))
        {
            //Open / close doors
            if ((subwaypausetime[i] == 720) || ((subwaypausetime[i] >= 120) && (subwaypausetime[i]-TICSPERFRAME < 120)))
                activatehitag(sector[subwaytracksector[i][0]].hitag);

            subwaypausetime[i] -= TICSPERFRAME;
            if (subwaypausetime[i] < 0)
            {
                subwaypausetime[i] = 720;
                if (subwayvel[i] < 0)
                {
                    subwaygoalstop[i]--;
                    if (subwaygoalstop[i] < 0)
                    {
                        subwaygoalstop[i] = 1;
                        subwayvel[i] = 1;
                    }
                }
                else if (subwayvel[i] > 0)
                {
                    subwaygoalstop[i]++;
                    if (subwaygoalstop[i] >= subwaystopcnt[i])
                    {
                        subwaygoalstop[i] = subwaystopcnt[i]-2;
                        subwayvel[i] = -1;
                    }
                }
            }
        }
    }
}

void statuslistcode(void)
{
    short p, target, hitobject, daang, osectnum, movestat;
    int i, nexti, j, nextj, k, l, dax, day, daz, dist=0, ox, oy, mindist;
    int doubvel, xvect, yvect;

    //Go through active BROWNMONSTER list
    for (i=headspritestat[1]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];

        k = krand();

        //Choose a target player
        mindist = 0x7fffffff; target = connecthead;
        for (p=connecthead; p>=0; p=connectpoint2[p])
        {
            dist = klabs(sprite[i].x-pos[p].x)+klabs(sprite[i].y-pos[p].y);
            if (dist < mindist) mindist = dist, target = p;
        }

        //brown monster decides to shoot bullet
        if ((k&63) == 23)
        {
            if (cansee(sprite[i].x,sprite[i].y,sprite[i].z-(tilesiz[sprite[i].picnum].y<<7),sprite[i].sectnum,pos[target].x,pos[target].y,pos[target].z,cursectnum[target]) == 0)
            {
                if ((k&0xf00) == 0xb00) changespritestat(i,2);
            }
            else
            {
                wsayfollow("monshoot.wav",5144L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,1);

                doubvel = TICSPERFRAME << ((ssync[target].bits&256) >> 7);
                xvect = 0, yvect = 0;
                if (ssync[target].fvel != 0)
                {
                    xvect += ((((int)ssync[target].fvel)*doubvel*(int)sintable[(ang[target]+512)&2047])>>3);
                    yvect += ((((int)ssync[target].fvel)*doubvel*(int)sintable[ang[target]&2047])>>3);
                }
                if (ssync[target].svel != 0)
                {
                    xvect += ((((int)ssync[target].svel)*doubvel*(int)sintable[ang[target]&2047])>>3);
                    yvect += ((((int)ssync[target].svel)*doubvel*(int)sintable[(ang[target]+1536)&2047])>>3);
                }

                ox = pos[target].x; oy = pos[target].y;

                //distance is j
                j = ksqrt((ox-sprite[i].x)*(ox-sprite[i].x)+(oy-sprite[i].y)*(oy-sprite[i].y));

                switch ((sprite[i].extra>>11)&3)
                {
                case 1: j = -(j>>1); break;
                case 3: j = 0; break;
                case 0: case 2: break;
                }
                sprite[i].extra += 2048;

                //rate is (TICSPERFRAME<<19)
                xvect = scale(xvect,j,TICSPERFRAME<<19);
                yvect = scale(yvect,j,TICSPERFRAME<<19);
                clipmove_old(&ox,&oy,&pos[target].z,&cursectnum[target],xvect<<14,yvect<<14,128L,4<<8,4<<8,CLIPMASK0);
                ox -= sprite[i].x;
                oy -= sprite[i].y;

                daang = ((getangle(ox,oy)+(krand()&7)-4)&2047);

                dax = (sintable[(daang+512)&2047]>>6);
                day = (sintable[daang&2047]>>6);
                daz = 0;
                if (ox != 0)
                    daz = scale(dax,pos[target].z+(8<<8)-sprite[i].z,ox);
                else if (oy != 0)
                    daz = scale(day,pos[target].z+(8<<8)-sprite[i].z,oy);

                spawnsprite(j,sprite[i].x,sprite[i].y,sprite[i].z,128,0,0,
                            16,sprite[i].xrepeat,sprite[i].yrepeat,0,0,BULLET,daang,dax,day,daz,i,sprite[i].sectnum,6,0,0,0);

                sprite[i].extra &= (~2047);
            }
        }

        //Move brown monster
        dax = sprite[i].x;   //Back up old x&y if stepping off cliff
        day = sprite[i].y;

        doubvel = max(mulscale7(sprite[i].xrepeat,sprite[i].yrepeat),4);

        osectnum = sprite[i].sectnum;
        movestat = movesprite((short)i,(int)sintable[(sprite[i].ang+512)&2047]*doubvel,(int)sintable[sprite[i].ang]*doubvel,0L,4L<<8,4L<<8,CLIPMASK0);
        if (globloz > sprite[i].z+(48<<8))
        { sprite[i].x = dax; sprite[i].y = day; movestat = 1; }
        else
            sprite[i].z = globloz-((tilesiz[sprite[i].picnum].y*sprite[i].yrepeat)<<1);

        if ((sprite[i].sectnum != osectnum) && (sector[sprite[i].sectnum].lotag == 10))
        { warpsprite((short)i); movestat = 0; }

        if ((movestat != 0) || ((k&63) == 1))
        {
            if (sprite[i].ang == (sprite[i].extra&2047))
            {
                daang = (getangle(pos[target].x-sprite[i].x,pos[target].y-sprite[i].y)&2047);
                daang = ((daang+(krand()&1023)-512)&2047);
                sprite[i].extra = ((sprite[i].extra&(~2047))|daang);
            }
            if ((sprite[i].extra-sprite[i].ang)&1024)
            {
                sprite[i].ang = ((sprite[i].ang-32)&2047);
                if (!((sprite[i].extra-sprite[i].ang)&1024)) sprite[i].ang = (sprite[i].extra&2047);
            }
            else
            {
                sprite[i].ang = ((sprite[i].ang+32)&2047);
                if (((sprite[i].extra-sprite[i].ang)&1024)) sprite[i].ang = (sprite[i].extra&2047);
            }
        }
    }

    for (i=headspritestat[10]; i>=0; i=nexti) //EVILAL list
    {
        nexti = nextspritestat[i];

        if (sprite[i].yrepeat < 38) continue;
        if (sprite[i].yrepeat < 64)
        {
            sprite[i].xrepeat++;
            sprite[i].yrepeat++;
            continue;
        }

        if ((nummoves-i)&statrate[10]) continue;

        //Choose a target player
        mindist = 0x7fffffff; target = connecthead;
        for (p=connecthead; p>=0; p=connectpoint2[p])
        {
            dist = klabs(sprite[i].x-pos[p].x)+klabs(sprite[i].y-pos[p].y);
            if (dist < mindist) mindist = dist, target = p;
        }

        k = (krand()&255);

        if ((sprite[i].lotag&32) && (k < 48))  //Al decides to reproduce
        {
            l = 0;
            if ((sprite[i].lotag&64) && (k < 2))  //Give him a chance to reproduce without seeing you
                l = 1;
            else if (cansee(sprite[i].x,sprite[i].y,sprite[i].z-(tilesiz[sprite[i].picnum].y<<7),sprite[i].sectnum,pos[target].x,pos[target].y,pos[target].z,cursectnum[target]) == 1)
                l = 1;
            if (l != 0)
            {
                spawnsprite(j,sprite[i].x,sprite[i].y,sprite[i].z,sprite[i].cstat,sprite[i].shade,sprite[i].pal,
                            sprite[i].clipdist,38,38,sprite[i].xoffset,sprite[i].yoffset,sprite[i].picnum,krand()&2047,0,0,0,i,
                            sprite[i].sectnum,10,sprite[i].lotag,sprite[i].hitag,sprite[i].extra);
                switch (krand()&31) //Mutations!
                {
                case 0: sprite[i].cstat ^= 2; break;
                case 1: sprite[i].cstat ^= 512; break;
                case 2: sprite[i].shade++; break;
                case 3: sprite[i].shade--; break;
                case 4: sprite[i].pal ^= 16; break;
                case 5: case 6: case 7: sprite[i].lotag ^= (1<<(krand()&7)); break;
                case 8: sprite[i].lotag = (krand()&255); break;
                }
            }
        }
        if (k >= 208+((sprite[i].lotag&128)>>2))    //Al decides to shoot bullet
        {
            if (cansee(sprite[i].x,sprite[i].y,sprite[i].z-(tilesiz[sprite[i].picnum].y<<7),sprite[i].sectnum,pos[target].x,pos[target].y,pos[target].z,cursectnum[target]) == 1)
            {
                wsayfollow("zipguns.wav",5144L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,1);

                spawnsprite(j,sprite[i].x,sprite[i].y,
                            sector[sprite[i].sectnum].floorz-(24<<8),
                            0,0,0,16,32,32,0,0,BULLET,
                            (getangle(pos[target].x-sprite[j].x,
                                      pos[target].y-sprite[j].y)+(krand()&15)-8)&2047,
                            sintable[(sprite[j].ang+512)&2047]>>6,
                            sintable[sprite[j].ang&2047]>>6,
                            ((pos[target].z+(8<<8)-sprite[j].z)<<8) /
                            (ksqrt((pos[target].x-sprite[j].x) *
                                   (pos[target].x-sprite[j].x) +
                                   (pos[target].y-sprite[j].y) *
                                   (pos[target].y-sprite[j].y))+1),
                            i,sprite[i].sectnum,6,0,0,0);
            }
        }

        //Move Al
        l = (((sprite[i].lotag&3)+2)<<8);
        if (sprite[i].lotag&4) l = -l;
        dax = sintable[(sprite[i].ang+512)&2047]*l;
        day = sintable[sprite[i].ang]*l;

        osectnum = sprite[i].sectnum;
        movestat = movesprite((short)i,dax,day,0L,-(8L<<8),-(8L<<8),CLIPMASK0);
        sprite[i].z = globloz;
        if ((sprite[i].sectnum != osectnum) && (sector[sprite[i].sectnum].lotag == 10))
        {
            warpsprite((short)i);
            movestat = 0;
        }

        if (sprite[i].lotag&16)
        {
            if (((k&124) >= 120) && (cansee(sprite[i].x,sprite[i].y,sprite[i].z-(tilesiz[sprite[i].picnum].y<<7),sprite[i].sectnum,pos[target].x,pos[target].y,pos[target].z,cursectnum[target]) == 1))
                sprite[i].ang = getangle(pos[target].x-sprite[i].x,pos[target].y-sprite[i].y);
            else
                sprite[i].ang = (krand()&2047);
        }

        if (movestat != 0)
        {
            if ((k&2) && (cansee(sprite[i].x,sprite[i].y,sprite[i].z-(tilesiz[sprite[i].picnum].y<<7),sprite[i].sectnum,pos[target].x,pos[target].y,pos[target].z,cursectnum[target]) == 1))
                sprite[i].ang = getangle(pos[target].x-sprite[i].x,pos[target].y-sprite[i].y);
            else
                sprite[i].ang = (krand()&2047);

            if ((movestat&49152) == 49152)
                if (sprite[movestat&16383].picnum == EVILAL)
                    if ((k&31) >= 30)
                    {
                        wsayfollow("blowup.wav",5144L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                        sprite[i].picnum = EVILALGRAVE;
                        sprite[i].cstat = 0;
                        sprite[i].xvel = (krand()&255)-128;
                        sprite[i].yvel = (krand()&255)-128;
                        sprite[i].zvel = (krand()&4095)-3072;
                        changespritestat(i,9);
                    }

            if (sprite[i].lotag&8)
                if ((k&31) >= 30)
                {
                    wsayfollow("blowup.wav",5144L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                    sprite[i].picnum = EVILALGRAVE;
                    sprite[i].cstat = 0;
                    sprite[i].xvel = (krand()&255)-128;
                    sprite[i].yvel = (krand()&255)-128;
                    sprite[i].zvel = (krand()&4095)-3072;
                    changespritestat(i,9);
                }

            if (movestat == -1)
            {
                wsayfollow("blowup.wav",5144L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                sprite[i].picnum = EVILALGRAVE;
                sprite[i].cstat = 0;
                sprite[i].xvel = (krand()&255)-128;
                sprite[i].yvel = (krand()&255)-128;
                sprite[i].zvel = (krand()&4095)-3072;
                changespritestat(i,9);
            }
        }
    }

    //Go through travelling bullet sprites
    for (i=headspritestat[6]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];

        if ((nummoves-i)&statrate[6]) continue;

        //If the sprite is a bullet then...
        if ((sprite[i].picnum == BULLET) || (sprite[i].picnum == GRABBER) || (sprite[i].picnum == MISSILE) || (sprite[i].picnum == BOMB))
        {
            dax = ((((int)sprite[i].xvel)*TICSPERFRAME)<<12);
            day = ((((int)sprite[i].yvel)*TICSPERFRAME)<<12);
            daz = ((((int)sprite[i].zvel)*TICSPERFRAME)>>2);
            if (sprite[i].picnum == BOMB) daz = 0;

            osectnum = sprite[i].sectnum;
            hitobject = movesprite((short)i,dax,day,daz,4L<<8,4L<<8,CLIPMASK1);
            if ((sprite[i].sectnum != osectnum) && (sector[sprite[i].sectnum].lotag == 10))
            {
                warpsprite((short)i);
                hitobject = 0;
            }

            if (sprite[i].picnum == GRABBER)     // Andy did this (& Ken) !Homing!
            {
                checkgrabbertouchsprite(i,sprite[i].sectnum);
                l = 0x7fffffff;
                for (j = connecthead; j >= 0; j = connectpoint2[j])   // Players
                    if (j != (sprite[i].owner & (MAXSPRITES - 1)))
                        if (cansee(sprite[i].x,sprite[i].y,sprite[i].z,sprite[i].sectnum,pos[j].x,pos[j].y,pos[j].z,cursectnum[j]))
                        {
                            k = ksqrt(ksqr(pos[j].x - sprite[i].x) + ksqr(pos[j].y - sprite[i].y) + (ksqr(pos[j].z - sprite[i].z) >> 8));
                            if (k < l)
                            {
                                l = k;
                                dax = (pos[j].x - sprite[i].x);
                                day = (pos[j].y - sprite[i].y);
                                daz = (pos[j].z - sprite[i].z);
                            }
                        }
                for (j = headspritestat[1]; j >= 0; j = nextj)    // Active monsters
                {
                    nextj = nextspritestat[j];
                    if (cansee(sprite[i].x,sprite[i].y,sprite[i].z,sprite[i].sectnum,sprite[j].x,sprite[j].y,sprite[j].z,sprite[j].sectnum))
                    {
                        k = ksqrt(ksqr(sprite[j].x - sprite[i].x) + ksqr(sprite[j].y - sprite[i].y) + (ksqr(sprite[j].z - sprite[i].z) >> 8));
                        if (k < l)
                        {
                            l = k;
                            dax = (sprite[j].x - sprite[i].x);
                            day = (sprite[j].y - sprite[i].y);
                            daz = (sprite[j].z - sprite[i].z);
                        }
                    }
                }
                for (j = headspritestat[2]; j >= 0; j = nextj)    // Inactive monsters
                {
                    nextj = nextspritestat[j];
                    if (cansee(sprite[i].x,sprite[i].y,sprite[i].z,sprite[i].sectnum,sprite[j].x,sprite[j].y,sprite[j].z,sprite[j].sectnum))
                    {
                        k = ksqrt(ksqr(sprite[j].x - sprite[i].x) + ksqr(sprite[j].y - sprite[i].y) + (ksqr(sprite[j].z - sprite[i].z) >> 8));
                        if (k < l)
                        {
                            l = k;
                            dax = (sprite[j].x - sprite[i].x);
                            day = (sprite[j].y - sprite[i].y);
                            daz = (sprite[j].z - sprite[i].z);
                        }
                    }
                }
                if (l != 0x7fffffff)
                {
                    sprite[i].xvel = (divscale7(dax,l) + sprite[i].xvel);   // 1/5 of velocity is homing, 4/5 is momentum
                    sprite[i].yvel = (divscale7(day,l) + sprite[i].yvel);   // 1/5 of velocity is homing, 4/5 is momentum
                    sprite[i].zvel = (divscale7(daz,l) + sprite[i].zvel);   // 1/5 of velocity is homing, 4/5 is momentum
                    l = ksqrt((sprite[i].xvel * sprite[i].xvel) + (sprite[i].yvel * sprite[i].yvel) + ((sprite[i].zvel * sprite[i].zvel) >> 8));
                    sprite[i].xvel = divscale9(sprite[i].xvel,l);
                    sprite[i].yvel = divscale9(sprite[i].yvel,l);
                    sprite[i].zvel = divscale9(sprite[i].zvel,l);
                    sprite[i].ang = getangle(sprite[i].xvel,sprite[i].yvel);
                }
            }

            if (sprite[i].picnum == BOMB)
            {
                j = sprite[i].sectnum;
                if ((sector[j].floorstat&2) && (sprite[i].z > globloz-(8<<8)))
                {
                    k = sector[j].wallptr;
                    daang = getangle(wall[wall[k].point2].x-wall[k].x,wall[wall[k].point2].y-wall[k].y);
                    sprite[i].xvel += mulscale22(sintable[(daang+1024)&2047],sector[j].floorheinum);
                    sprite[i].yvel += mulscale22(sintable[(daang+512)&2047],sector[j].floorheinum);
                }
            }

            if (sprite[i].picnum == BOMB)
            {
                sprite[i].z += sprite[i].zvel;
                sprite[i].zvel += (TICSPERFRAME<<7);
                if (sprite[i].z < globhiz+(tilesiz[BOMB].y<<6))
                {
                    sprite[i].z = globhiz+(tilesiz[BOMB].y<<6);
                    sprite[i].zvel = -(sprite[i].zvel>>1);
                }
                if (sprite[i].z > globloz-(tilesiz[BOMB].y<<6))
                {
                    sprite[i].z = globloz-(tilesiz[BOMB].y<<6);
                    sprite[i].zvel = -(sprite[i].zvel>>1);
                }
                dax = sprite[i].xvel; day = sprite[i].yvel;
                dist = dax*dax+day*day;
                if (dist < 512)
                {
                    bombexplode(i);
                    goto bulletisdeletedskip;
                }
                if (dist < 4096)
                {
                    sprite[i].xrepeat = ((4096+2048)*16) / (dist+2048);
                    sprite[i].yrepeat = sprite[i].xrepeat;
                    sprite[i].xoffset = (krand()&15)-8;
                    sprite[i].yoffset = (krand()&15)-8;
                }
                if (mulscale30(krand(),dist) == 0)
                {
                    sprite[i].xvel -= ksgn(sprite[i].xvel);
                    sprite[i].yvel -= ksgn(sprite[i].yvel);
                    sprite[i].zvel -= ksgn(sprite[i].zvel);
                }
            }

            //Check for bouncy objects before killing bullet
            if ((hitobject&0xc000) == 16384)  //Bullet hit a ceiling/floor
            {
                k = sector[hitobject&(MAXSECTORS-1)].wallptr; l = wall[k].point2;
                daang = getangle(wall[l].x-wall[k].x,wall[l].y-wall[k].y);

                getzsofslope(hitobject&(MAXSECTORS-1),sprite[i].x,sprite[i].y,&k,&l);
                if (sprite[i].z < ((k+l)>>1)) k = sector[hitobject&(MAXSECTORS-1)].ceilingheinum;
                else k = sector[hitobject&(MAXSECTORS-1)].floorheinum;

                dax = mulscale14(k,sintable[(daang)&2047]);
                day = mulscale14(k,sintable[(daang+1536)&2047]);
                daz = 4096;

                k = sprite[i].xvel*dax+sprite[i].yvel*day+mulscale4(sprite[i].zvel,daz);
                l = dax*dax+day*day+daz*daz;
                if ((klabs(k)>>14) < l)
                {
                    k = divscale17(k,l);
                    sprite[i].xvel -= mulscale16(dax,k);
                    sprite[i].yvel -= mulscale16(day,k);
                    sprite[i].zvel -= mulscale12(daz,k);
                }
                wsayfollow("bouncy.wav",4096L+(krand()&127)-64,255,&sprite[i].x,&sprite[i].y,1);
                hitobject = 0;
                sprite[i].owner = -1;   //Bullet turns evil!
            }
            else if ((hitobject&0xc000) == 32768)  //Bullet hit a wall
            {
                if (wall[hitobject&4095].lotag == 8)
                {
                    dax = sprite[i].xvel; day = sprite[i].yvel;
                    if ((sprite[i].picnum != BOMB) || (dax*dax+day*day >= 512))
                    {
                        k = (hitobject&4095); l = wall[k].point2;
                        j = getangle(wall[l].x-wall[k].x,wall[l].y-wall[k].y)+512;

                        //k = cos(ang) * sin(ang) * 2
                        k = mulscale13(sintable[(j+512)&2047],sintable[j&2047]);
                        //l = cos(ang * 2)
                        l = sintable[((j<<1)+512)&2047];

                        ox = sprite[i].xvel; oy = sprite[i].yvel;
                        dax = -ox; day = -oy;
                        sprite[i].xvel = dmulscale14(day,k,dax,l);
                        sprite[i].yvel = dmulscale14(dax,k,-day,l);

                        if (sprite[i].picnum == BOMB)
                        {
                            sprite[i].xvel -= (sprite[i].xvel>>3);
                            sprite[i].yvel -= (sprite[i].yvel>>3);
                            sprite[i].zvel -= (sprite[i].zvel>>3);
                        }
                        ox -= sprite[i].xvel; oy -= sprite[i].yvel;
                        dist = ((ox*ox+oy*oy)>>8);
                        wsayfollow("bouncy.wav",4096L+(krand()&127)-64,min(dist,256),&sprite[i].x,&sprite[i].y,1);
                        hitobject = 0;
                        sprite[i].owner = -1;   //Bullet turns evil!
                    }
                }
            }
            else if ((hitobject&0xc000) == 49152)  //Bullet hit a sprite
            {
                if (sprite[hitobject&4095].picnum == BOUNCYMAT)
                {
                    if ((sprite[hitobject&4095].cstat & CSTAT_SPRITE_ALIGNMENT) == CSTAT_SPRITE_ALIGNMENT_FACING)
                    {
                        sprite[i].xvel = -sprite[i].xvel;
                        sprite[i].yvel = -sprite[i].yvel;
                        sprite[i].zvel = -sprite[i].zvel;
                        dist = 255;
                    }
                    else if ((sprite[hitobject&4095].cstat & CSTAT_SPRITE_ALIGNMENT) == CSTAT_SPRITE_ALIGNMENT_WALL)
                    {
                        j = sprite[hitobject&4095].ang;

                        //k = cos(ang) * sin(ang) * 2
                        k = mulscale13(sintable[(j+512)&2047],sintable[j&2047]);
                        //l = cos(ang * 2)
                        l = sintable[((j<<1)+512)&2047];

                        ox = sprite[i].xvel; oy = sprite[i].yvel;
                        dax = -ox; day = -oy;
                        sprite[i].xvel = dmulscale14(day,k,dax,l);
                        sprite[i].yvel = dmulscale14(dax,k,-day,l);

                        ox -= sprite[i].xvel; oy -= sprite[i].yvel;
                        dist = ((ox*ox+oy*oy)>>8);
                    }
                    sprite[i].owner = -1;   //Bullet turns evil!
                    wsayfollow("bouncy.wav",4096L+(krand()&127)-64,min(dist,256),&sprite[i].x,&sprite[i].y,1);
                    hitobject = 0;
                }
            }

            if (hitobject != 0)
            {
                if ((sprite[i].picnum == MISSILE) || (sprite[i].picnum == BOMB))
                {
                    if ((hitobject&0xc000) == 49152)
                        if (sprite[hitobject&4095].lotag == 5)  //Basketball hoop
                        {
                            wsayfollow("niceshot.wav",3840L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                            deletesprite((short)i);
                            goto bulletisdeletedskip;
                        }

                    bombexplode(i);
                    goto bulletisdeletedskip;
                }

                if ((hitobject&0xc000) == 16384)  //Hits a ceiling / floor
                {
                    wsayfollow("bullseye.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                    deletesprite((short)i);
                    goto bulletisdeletedskip;
                }
                else if ((hitobject&0xc000) == 32768)  //Bullet hit a wall
                {
                    if (wall[hitobject&4095].picnum == KENPICTURE)
                    {
                        if (waloff[MAXTILES-1] != 0)
                            wall[hitobject&4095].picnum = MAXTILES-1;
                        wsayfollow("hello.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);   //Ken says, "Hello... how are you today!"
                    }
                    else
                        wsayfollow("bullseye.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);

                    deletesprite((short)i);
                    goto bulletisdeletedskip;
                }
                else if ((hitobject&0xc000) == 49152)  //Bullet hit a sprite
                {
                    if ((sprite[hitobject&4095].lotag == 5) && (sprite[i].picnum == GRABBER))    // Basketball hoop (Andy's addition)
                    {
                        wsayfollow("niceshot.wav",3840L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                        switch (krand() & 63)
                        {
                        case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9:
                            sprite[i].picnum = COIN; break;
                        case 10: case 11: case 12: case 13: case 14: case 15: case 16:
                            sprite[i].picnum = DIAMONDS; break;
                        case 17: case 18: case 19:
                            sprite[i].picnum = COINSTACK; break;
                        case 20: case 21: case 22: case 23:
                            sprite[i].picnum = GIFTBOX; break;
                        case 24: case 25:
                            sprite[i].picnum = GRABCANNON; break;
                        case 26: case 27:
                            sprite[i].picnum = LAUNCHER; break;
                        case 28: case 29: case 30:
                            sprite[i].picnum = CANNON; break;
                        case 31:
                            sprite[i].picnum = AIRPLANE; break;
                        default:
                            deletesprite((short)i);
                            goto bulletisdeletedskip;
                        }
                        sprite[i].xvel = sprite[i].yvel = sprite[i].zvel = 0;
                        sprite[i].cstat &= ~0x83;    //Should not clip, foot-z
                        changespritestat(i,12);
                        goto bulletisdeletedskip;
                    }

                    //Check if bullet hit a player & find which player it was...
                    if (sprite[hitobject&4095].picnum == PLAYER)
                        for (j=connecthead; j>=0; j=connectpoint2[j])
                            if (sprite[i].owner != j+4096)
                                if (playersprite[j] == (hitobject&4095))
                                {
                                    wsayfollow("ouch.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                                    if (sprite[i].picnum == GRABBER)     // Andy did this
                                    {
                                        k = ((sprite[i].xrepeat * sprite[i].yrepeat) * 3) >> 9;
                                        changehealth((sprite[i].owner - 4096),k);
                                        changehealth(j,-k);
                                    }
                                    else changehealth(j,-mulscale8(sprite[i].xrepeat,sprite[i].yrepeat));
                                    deletesprite((short)i);
                                    goto bulletisdeletedskip;
                                }

                    //Check if bullet hit any monsters...
                    j = (hitobject&4095);     //j is the spritenum that the bullet (spritenum i) hit
                    if (sprite[i].owner != j)
                    {
                        switch (sprite[j].picnum)
                        {
                        case BROWNMONSTER:
                            if (sprite[j].lotag > 0)
                            {
                                if (sprite[i].picnum == GRABBER)     // Andy did this
                                {
                                    k = ((sprite[i].xrepeat * sprite[i].yrepeat) * 3) >> 9;
                                    changehealth((sprite[i].owner - 4096),k);
                                    sprite[j].lotag -= k;
                                }
                                sprite[j].lotag -= mulscale8(sprite[i].xrepeat,sprite[i].yrepeat);
                            }
                            if (sprite[j].lotag > 0)
                            {
                                if (sprite[j].lotag <= 25) sprite[j].cstat |= 2;
                                wsayfollow("hurt.wav",4096L+(krand()&511)-256,256L,&sprite[i].x,&sprite[i].y,1);
                            }
                            else
                            {
                                wsayfollow("mondie.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                                sprite[j].z += ((tilesiz[sprite[j].picnum].y*sprite[j].yrepeat)<<1);
                                sprite[j].picnum = GIFTBOX;
                                sprite[j].cstat &= ~0x83;    //Should not clip, foot-z

                                spawnsprite(k,sprite[j].x,sprite[j].y,sprite[j].z,
                                            0,-4,0,32,64,64,0,0,EXPLOSION,sprite[j].ang,
                                            0,0,0,j,sprite[j].sectnum,5,31,0,0);
                                //31=Time left for explosion to stay

                                changespritestat(j,12);
                            }
                            deletesprite((short)i);
                            goto bulletisdeletedskip;
                        case EVILAL:
                            wsayfollow("blowup.wav",5144L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                            sprite[j].picnum = EVILALGRAVE;
                            sprite[j].cstat = 0;
                            sprite[j].xvel = (krand()&255)-128;
                            sprite[j].yvel = (krand()&255)-128;
                            sprite[j].zvel = (krand()&4095)-3072;
                            changespritestat(j,9);

                            deletesprite((short)i);
                            goto bulletisdeletedskip;
                        case AL:
                            wsayfollow("blowup.wav",5144L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                            sprite[j].xrepeat += 2;
                            sprite[j].yrepeat += 2;
                            if (sprite[j].yrepeat >= 38)
                            {
                                sprite[j].picnum = EVILAL;
                                //sprite[j].cstat |= 2;      //Make him transluscent
                                changespritestat(j,10);
                            }
                            deletesprite((short)i);
                            goto bulletisdeletedskip;
                        default:
                            wsayfollow("bullseye.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
                            deletesprite((short)i);
                            goto bulletisdeletedskip;
                        }
                    }
                }
            }
        }
bulletisdeletedskip: continue;
    }

    //Go through monster waiting for you list
    for (i=headspritestat[2]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];

        if ((nummoves-i)&15) continue;

        //Use dot product to see if monster's angle is towards a player
        for (p=connecthead; p>=0; p=connectpoint2[p])
            if (sintable[(sprite[i].ang+512)&2047]*(pos[p].x-sprite[i].x) + sintable[sprite[i].ang&2047]*(pos[p].y-sprite[i].y) >= 0)
                if (cansee(sprite[i].x,sprite[i].y,sprite[i].z-(tilesiz[sprite[i].picnum].y<<7),sprite[i].sectnum,pos[p].x,pos[p].y,pos[p].z,cursectnum[p]) == 1)
                {
                    changespritestat(i,1);
                    //if (sprite[i].lotag == 100)
                    //{
                    wsayfollow("iseeyou.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,1);
                    //   sprite[i].lotag = 99;
                    //}
                }
    }

    //Go through smoke sprites
    for (i=headspritestat[3]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];

        sprite[i].z -= (TICSPERFRAME<<6);
        sprite[i].lotag -= TICSPERFRAME;
        if ((int16_t)sprite[i].lotag < 0) deletesprite(i);
    }

    //Go through splash sprites
    for (i=headspritestat[4]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];

        sprite[i].lotag -= TICSPERFRAME;
        sprite[i].picnum = SPLASH + ((63-sprite[i].lotag)>>4);
        if ((int16_t)sprite[i].lotag < 0) deletesprite(i);
    }

    //Go through explosion sprites
    for (i=headspritestat[5]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];

        sprite[i].lotag -= TICSPERFRAME;
        if ((int16_t)sprite[i].lotag < 0) deletesprite(i);
    }

    //Go through bomb spriral-explosion sprites
    for (i=headspritestat[7]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];

        sprite[i].xrepeat = (sprite[i].lotag>>2);
        sprite[i].yrepeat = (sprite[i].lotag>>2);
        sprite[i].lotag -= (TICSPERFRAME<<2);
        if ((int16_t)sprite[i].lotag < 0) { deletesprite(i); continue; }

        if ((nummoves-i)&statrate[7]) continue;

        sprite[i].x += ((sprite[i].xvel*TICSPERFRAME)>>2);
        sprite[i].y += ((sprite[i].yvel*TICSPERFRAME)>>2);
        sprite[i].z += ((sprite[i].zvel*TICSPERFRAME)>>2);

        sprite[i].zvel += (TICSPERFRAME<<9);
        if (sprite[i].z < sector[sprite[i].sectnum].ceilingz+(4<<8))
        {
            sprite[i].z = sector[sprite[i].sectnum].ceilingz+(4<<8);
            sprite[i].zvel = -(sprite[i].zvel>>1);
        }
        if (sprite[i].z > sector[sprite[i].sectnum].floorz-(4<<8))
        {
            sprite[i].z = sector[sprite[i].sectnum].floorz-(4<<8);
            sprite[i].zvel = -(sprite[i].zvel>>1);
        }
    }

    //EVILALGRAVE shrinking list
    for (i=headspritestat[9]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];

        sprite[i].xrepeat = (sprite[i].lotag>>2);
        sprite[i].yrepeat = (sprite[i].lotag>>2);
        sprite[i].lotag -= TICSPERFRAME;
        if ((int16_t)sprite[i].lotag < 0) { deletesprite(i); continue; }

        if ((nummoves-i)&statrate[9]) continue;

        sprite[i].x += (sprite[i].xvel*TICSPERFRAME);
        sprite[i].y += (sprite[i].yvel*TICSPERFRAME);
        sprite[i].z += (sprite[i].zvel*TICSPERFRAME);

        sprite[i].zvel += (TICSPERFRAME<<8);
        if (sprite[i].z < sector[sprite[i].sectnum].ceilingz)
        {
            sprite[i].z = sector[sprite[i].sectnum].ceilingz;
            sprite[i].xvel -= (sprite[i].xvel>>2);
            sprite[i].yvel -= (sprite[i].yvel>>2);
            sprite[i].zvel = -(sprite[i].zvel>>1);
        }
        if (sprite[i].z > sector[sprite[i].sectnum].floorz)
        {
            sprite[i].z = sector[sprite[i].sectnum].floorz;
            sprite[i].xvel -= (sprite[i].xvel>>2);
            sprite[i].yvel -= (sprite[i].yvel>>2);
            sprite[i].zvel = -(sprite[i].zvel>>1);
        }
    }

    //Re-spawning sprite list
    for (i=headspritestat[11]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];

        sprite[i].extra -= TICSPERFRAME;
        if (sprite[i].extra < 0)
        {
            wsayfollow("warp.wav",6144L+(krand()&127)-64,128L,&sprite[i].x,&sprite[i].y,0);
            sprite[i].cstat &= (uint16_t) ~0x8000;
            sprite[i].extra = -1;
            changespritestat((short)i,0);
        }
    }
}

void activatehitag(short dahitag)
{
    int i, nexti;

    for (i=0; i<numsectors; i++)
        if (sector[i].hitag == dahitag) operatesector(i);

    for (i=headspritestat[0]; i>=0; i=nexti)
    {
        nexti = nextspritestat[i];
        if (sprite[i].hitag == dahitag) operatesprite(i);
    }
}

void bombexplode(int i)
{
    int j, nextj, k, daang, dax, day, dist;

    spawnsprite(j,sprite[i].x,sprite[i].y,sprite[i].z,0,-4,0,
                32,64,64,0,0,EXPLOSION,sprite[i].ang,
                0,0,0,sprite[i].owner,sprite[i].sectnum,5,31,0,0);
    //31=Time left for explosion to stay

    for (k=0; k<12; k++)
    {
        spawnsprite(j,sprite[i].x,sprite[i].y,sprite[i].z+(8<<8),2,-4,0,
                    32,24,24,0,0,EXPLOSION,sprite[i].ang,
                    (krand()>>7)-256,(krand()>>7)-256,(krand()>>2)-8192,
                    sprite[i].owner,sprite[i].sectnum,7,96,0,0);
        //96=Time left for smoke to be alive
    }

    for (j=connecthead; j>=0; j=connectpoint2[j])
    {
        dist = (pos[j].x-sprite[i].x)*(pos[j].x-sprite[i].x);
        dist += (pos[j].y-sprite[i].y)*(pos[j].y-sprite[i].y);
        dist += ((pos[j].z-sprite[i].z)>>4)*((pos[j].z-sprite[i].z)>>4);
        if (dist < 4194304)
            if (cansee(sprite[i].x,sprite[i].y,sprite[i].z-(tilesiz[sprite[i].picnum].y<<7),sprite[i].sectnum,pos[j].x,pos[j].y,pos[j].z,cursectnum[j]) == 1)
            {
                k = ((32768/((dist>>16)+4))>>5);
                if (j == myconnectindex)
                {
                    daang = getangle(pos[j].x-sprite[i].x,pos[j].y-sprite[i].y);
                    dax = ((k*sintable[(daang+512)&2047])>>14);
                    day = ((k*sintable[daang&2047])>>14);
                    fvel += ((dax*sintable[(ang[j]+512)&2047]+day*sintable[ang[j]&2047])>>14);
                    svel += ((day*sintable[(ang[j]+512)&2047]-dax*sintable[ang[j]&2047])>>14);
                }
                changehealth(j,-k);    //if changehealth returns 1, you're dead
            }
    }

    for (k=1; k<=2; k++)      //Check for hurting monsters
    {
        for (j=headspritestat[k]; j>=0; j=nextj)
        {
            nextj = nextspritestat[j];

            dist = (sprite[j].x-sprite[i].x)*(sprite[j].x-sprite[i].x);
            dist += (sprite[j].y-sprite[i].y)*(sprite[j].y-sprite[i].y);
            dist += ((sprite[j].z-sprite[i].z)>>4)*((sprite[j].z-sprite[i].z)>>4);
            if (dist >= 4194304) continue;
            if (cansee(sprite[i].x,sprite[i].y,sprite[i].z-(tilesiz[sprite[i].picnum].y<<7),sprite[i].sectnum,sprite[j].x,sprite[j].y,sprite[j].z-(tilesiz[sprite[j].picnum].y<<7),sprite[j].sectnum) == 0)
                continue;
            if (sprite[j].picnum == BROWNMONSTER)
            {
                sprite[j].z += ((tilesiz[sprite[j].picnum].y*sprite[j].yrepeat)<<1);
                sprite[j].picnum = GIFTBOX;
                sprite[j].cstat &= ~0x83;    //Should not clip, foot-z
                changespritestat(j,12);
            }
        }
    }

    for (j=headspritestat[10]; j>=0; j=nextj) //Check for EVILAL's
    {
        nextj = nextspritestat[j];

        dist = (sprite[j].x-sprite[i].x)*(sprite[j].x-sprite[i].x);
        dist += (sprite[j].y-sprite[i].y)*(sprite[j].y-sprite[i].y);
        dist += ((sprite[j].z-sprite[i].z)>>4)*((sprite[j].z-sprite[i].z)>>4);
        if (dist >= 4194304) continue;
        if (cansee(sprite[i].x,sprite[i].y,sprite[i].z-(tilesiz[sprite[i].picnum].y<<7),sprite[i].sectnum,sprite[j].x,sprite[j].y,sprite[j].z-(tilesiz[sprite[j].picnum].y<<7),sprite[j].sectnum) == 0)
            continue;

        sprite[j].picnum = EVILALGRAVE;
        sprite[j].cstat = 0;
        sprite[j].xvel = (krand()&255)-128;
        sprite[j].yvel = (krand()&255)-128;
        sprite[j].zvel = (krand()&4095)-3072;
        changespritestat(j,9);
    }

    wsayfollow("blowup.wav",3840L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,0);
    deletesprite((short)i);
}

void processinput(short snum)
{
    // int oldposx, oldposy, nexti;
    int i, j, k, doubvel, xvect, yvect, goalz;
    int dax, day /*, dax2, day2, odax, oday, odax2, oday2*/;
    // short startwall, endwall;
    // char *ptr;

    //SHARED KEYS:
    //Movement code
    if ((ssync[snum].fvel|ssync[snum].svel) != 0)
    {
        doubvel = TICSPERFRAME << ((ssync[snum].bits&256) >> 7);

        xvect = 0, yvect = 0;
        if (ssync[snum].fvel != 0)
        {
            xvect += ((((int)ssync[snum].fvel)*doubvel*(int)sintable[(ang[snum]+512)&2047])>>3);
            yvect += ((((int)ssync[snum].fvel)*doubvel*(int)sintable[ang[snum]&2047])>>3);
        }
        if (ssync[snum].svel != 0)
        {
            xvect += ((((int)ssync[snum].svel)*doubvel*(int)sintable[ang[snum]&2047])>>3);
            yvect += ((((int)ssync[snum].svel)*doubvel*(int)sintable[(ang[snum]+1536)&2047])>>3);
        }
        if (flytime[snum] > lockclock) { xvect += xvect; yvect += yvect; }   // DOuble flying speed
        clipmove(&pos[snum],&cursectnum[snum],xvect,yvect,128L,4<<8,4<<8,CLIPMASK0);
        revolvedoorstat[snum] = 1;
    }
    else
    {
        revolvedoorstat[snum] = 0;
    }

    sprite[playersprite[snum]].cstat &= ~1;
    //Push player away from walls if clipmove doesn't work
    if (pushmove(&pos[snum],&cursectnum[snum],128L,4<<8,4<<8,CLIPMASK0) < 0)
        changehealth(snum,-1000);  //If this screws up, then instant death!!!

    // Getzrange returns the highest and lowest z's for an entire box,
    // NOT just a point.  This prevents you from falling off cliffs
    // when you step only slightly over the cliff.
    getzrange(&pos[snum],cursectnum[snum],&globhiz,&globhihit,&globloz,&globlohit,128L,CLIPMASK0);
    sprite[playersprite[snum]].cstat |= 1;

    if (ssync[snum].avel != 0)          //ang += avel * constant
    {
        //ENGINE calculates avel for you
        doubvel = TICSPERFRAME;
        if ((ssync[snum].bits&256) > 0)  //Lt. shift makes turn velocity 50% faster
            doubvel += (TICSPERFRAME>>1);
        ang[snum] += ((((int)ssync[snum].avel)*doubvel)>>4);
        ang[snum] &= 2047;
    }

    if (health[snum] < 0)
    {
        health[snum] -= TICSPERFRAME;
        if (health[snum] <= -160)
        {
            hvel[snum] = 0;
            if (snum == myconnectindex)
                fvel = 0, svel = 0, avel = 0, keystatus[3] = 1;

            deaths[snum]++;
            health[snum] = 100;
            numbombs[snum] = 0;
            numgrabbers[snum] = 0;
            nummissiles[snum] = 0;
            flytime[snum] = 0;

            findrandomspot(&pos[snum].x,&pos[snum].y,&cursectnum[snum]);
            pos[snum].z = getflorzofslope(cursectnum[snum],pos[snum].x,pos[snum].y)-(1<<8);
            horiz[snum] = 100;
            ang[snum] = (krand()&2047);

            sprite[playersprite[snum]].x = pos[snum].x;
            sprite[playersprite[snum]].y = pos[snum].y;
            sprite[playersprite[snum]].z = pos[snum].z+EYEHEIGHT;
            sprite[playersprite[snum]].picnum = PLAYER;
            sprite[playersprite[snum]].ang = ang[snum];
            sprite[playersprite[snum]].xrepeat = 64;
            sprite[playersprite[snum]].yrepeat = 64;
            changespritesect(playersprite[snum],cursectnum[snum]);

            drawstatusbar(snum);   // Andy did this

            i = playersprite[snum];
            wsayfollow("zipguns.wav",4096L+(krand()&127)-64,256L,&sprite[i].x,&sprite[i].y,1);
            for (k=0; k<16; k++)
            {
                spawnsprite(j,sprite[i].x,sprite[i].y,sprite[i].z+(8<<8),2,-4,0,
                            32,24,24,0,0,EXPLOSION,sprite[i].ang,
                            (krand()&511)-256,(krand()&511)-256,(krand()&16384)-8192,
                            sprite[i].owner,sprite[i].sectnum,7,96,0,0);
                //96=Time left for smoke to be alive
            }
        }
        else
        {
            sprite[playersprite[snum]].xrepeat = max(((128+health[snum])>>1),0);
            sprite[playersprite[snum]].yrepeat = max(((128+health[snum])>>1),0);

            hvel[snum] += (TICSPERFRAME<<2);
            horiz[snum] = max(horiz[snum]-4,0);
            pos[snum].z += hvel[snum];
            if (pos[snum].z > globloz-(4<<8))
            {
                pos[snum].z = globloz-(4<<8);
                horiz[snum] = min(horiz[snum]+5,200);
                hvel[snum] = 0;
            }
        }
    }

    if (((ssync[snum].bits&8) > 0) && (horiz[snum] > 100-(200>>1))) horiz[snum] -= 4;     //-
    if (((ssync[snum].bits&4) > 0) && (horiz[snum] < 100+(200>>1))) horiz[snum] += 4;   //+

    goalz = globloz-EYEHEIGHT;
    if (sector[cursectnum[snum]].lotag == 4)   //slime sector
        if ((globlohit&0xc000) != 49152)            //You're not on a sprite
        {
            goalz = globloz-(8<<8);
            if (pos[snum].z >= goalz-(2<<8))
            {
                clipmove(&pos[snum],&cursectnum[snum],-(TICSPERFRAME<<14),-(TICSPERFRAME<<14),128L,4<<8,4<<8,CLIPMASK0);

                if (slimesoundcnt[snum] >= 0)
                {
                    slimesoundcnt[snum] -= TICSPERFRAME;
                    while (slimesoundcnt[snum] < 0)
                    {
                        slimesoundcnt[snum] += 120;
                        wsayfollow("slime.wav",4096L+(krand()&127)-64,256L,&pos[snum].x,&pos[snum].y,1);
                    }
                }
            }
        }
    if (goalz < globhiz+(16<<8))   //ceiling&floor too close
        goalz = ((globloz+globhiz)>>1);
    //goalz += mousz;
    if (health[snum] >= 0)
    {
        if ((ssync[snum].bits&1) > 0)                         //A (stand high)
        {
            if (flytime[snum] <= lockclock)
            {
                if (pos[snum].z >= globloz-(32<<8))
                {
                    goalz -= (16<<8);
                    if (ssync[snum].bits&256) goalz -= (24<<8);
                }
            }
            else
            {
                hvel[snum] -= 192;
                if (ssync[snum].bits&256) hvel[snum] -= 192;
            }
        }
        if ((ssync[snum].bits&2) > 0)                         //Z (stand low)
        {
            if (flytime[snum] <= lockclock)
            {
                goalz += (12<<8);
                if (ssync[snum].bits&256) goalz += (12<<8);
            }
            else
            {
                hvel[snum] += 192;
                if (ssync[snum].bits&256) hvel[snum] += 192;
            }
        }
    }

    if (flytime[snum] <= lockclock)
    {
        if (pos[snum].z < goalz)
            hvel[snum] += (TICSPERFRAME<<4);
        else
            hvel[snum] = (((goalz-pos[snum].z)*TICSPERFRAME)>>5);
    }
    else
    {
        hvel[snum] -= (hvel[snum]>>2);
        hvel[snum] -= ksgn(hvel[snum]);
    }

    pos[snum].z += hvel[snum];
    if (pos[snum].z > globloz-(4<<8)) pos[snum].z = globloz-(4<<8), hvel[snum] = 0;
    if (pos[snum].z < globhiz+(4<<8)) pos[snum].z = globhiz+(4<<8), hvel[snum] = 0;

    if (dimensionmode[snum] != 3)
    {
        if (((ssync[snum].bits&32) > 0) && (zoom[snum] > 48)) zoom[snum] -= (zoom[snum]>>4);
        if (((ssync[snum].bits&16) > 0) && (zoom[snum] < 4096)) zoom[snum] += (zoom[snum]>>4);
    }

    //Update sprite representation of player
    //   -should be after movement, but before shooting code
    setsprite_eyeheight(playersprite[snum],&pos[snum]);
    sprite[playersprite[snum]].ang = ang[snum];

    if (health[snum] >= 0)
    {
        if ((cursectnum[snum] < 0) || (cursectnum[snum] >= numsectors))
        {
            //How did you get in the wrong sector?
            wsayfollow("ouch.wav",4096L+(krand()&127)-64,64L,&pos[snum].x,&pos[snum].y,1);
            changehealth(snum,-TICSPERFRAME);
        }
        else if (globhiz+(8<<8) > globloz)
        {
            //Ceiling and floor are smooshing you!
            wsayfollow("ouch.wav",4096L+(krand()&127)-64,64L,&pos[snum].x,&pos[snum].y,1);
            changehealth(snum,-TICSPERFRAME);
        }
    }

    if ((waterfountainwall[snum] >= 0) && (health[snum] >= 0))
        if ((wall[neartagwall].lotag != 7) || ((ssync[snum].bits&1024) == 0))
        {
            i = waterfountainwall[snum];
            if (wall[i].overpicnum == USEWATERFOUNTAIN)
                wall[i].overpicnum = WATERFOUNTAIN;
            else if (wall[i].picnum == USEWATERFOUNTAIN)
                wall[i].picnum = WATERFOUNTAIN;

            waterfountainwall[snum] = -1;
        }

    if ((ssync[snum].bits&1024) > 0)  //Space bar
    {
        //Continuous triggers...

        neartag(pos[snum].x,pos[snum].y,pos[snum].z,cursectnum[snum],ang[snum],&neartagsector,&neartagwall,&neartagsprite,&neartaghitdist,1024L,3,NULL);
        if (neartagsector == -1)
        {
            i = cursectnum[snum];
            if ((sector[i].lotag|sector[i].hitag) != 0)
                neartagsector = i;
        }

        if ((unsigned)neartagwall < MAXWALLS && wall[neartagwall].lotag == 7)  //Water fountain
        {
            if (wall[neartagwall].overpicnum == WATERFOUNTAIN)
            {
                wsayfollow("water.wav",4096L+(krand()&127)-64,256L,&pos[snum].x,&pos[snum].y,1);
                wall[neartagwall].overpicnum = USEWATERFOUNTAIN;
                waterfountainwall[snum] = neartagwall;
            }
            else if (wall[neartagwall].picnum == WATERFOUNTAIN)
            {
                wsayfollow("water.wav",4096L+(krand()&127)-64,256L,&pos[snum].x,&pos[snum].y,1);
                wall[neartagwall].picnum = USEWATERFOUNTAIN;
                waterfountainwall[snum] = neartagwall;
            }

            if (waterfountainwall[snum] >= 0)
            {
                waterfountaincnt[snum] -= TICSPERFRAME;
                while (waterfountaincnt[snum] < 0)
                {
                    waterfountaincnt[snum] += 120;
                    wsayfollow("water.wav",4096L+(krand()&127)-64,256L,&pos[snum].x,&pos[snum].y,1);
                    changehealth(snum,2);
                }
            }
        }

        //1-time triggers...
        if ((oflags[snum]&1024) == 0)
        {
            if (neartagsector >= 0)
                if (sector[neartagsector].hitag == 0)
                    operatesector(neartagsector);

            if (neartagwall >= 0)
                if (wall[neartagwall].lotag == 2)  //Switch
                {
                    activatehitag(wall[neartagwall].hitag);

                    j = wall[neartagwall].overpicnum;
                    if (j == SWITCH1ON)                     //1-time switch
                    {
                        wall[neartagwall].overpicnum = GIFTBOX;
                        wall[neartagwall].lotag = 0;
                        wall[neartagwall].hitag = 0;
                    }
                    if (j == GIFTBOX)                       //1-time switch
                    {
                        wall[neartagwall].overpicnum = SWITCH1ON;
                        wall[neartagwall].lotag = 0;
                        wall[neartagwall].hitag = 0;
                    }
                    if (j == SWITCH2ON) wall[neartagwall].overpicnum = SWITCH2OFF;
                    if (j == SWITCH2OFF) wall[neartagwall].overpicnum = SWITCH2ON;
                    if (j == SWITCH3ON) wall[neartagwall].overpicnum = SWITCH3OFF;
                    if (j == SWITCH3OFF) wall[neartagwall].overpicnum = SWITCH3ON;

                    i = wall[neartagwall].point2;
                    dax = ((wall[neartagwall].x+wall[i].x)>>1);
                    day = ((wall[neartagwall].y+wall[i].y)>>1);
                    wsayfollow("switch.wav",4096L+(krand()&255)-128,256L,&dax,&day,0);
                }

            if (neartagsprite >= 0)
            {
                if (sprite[neartagsprite].lotag == 1)
                {
                    //if you're shoving innocent little AL around, he gets mad!
                    if (sprite[neartagsprite].picnum == AL)
                    {
                        sprite[neartagsprite].picnum = EVILAL;
                        sprite[neartagsprite].cstat |= 2;   //Make him transluscent
                        sprite[neartagsprite].xrepeat = 38;
                        sprite[neartagsprite].yrepeat = 38;
                        changespritestat(neartagsprite,10);
                    }
                }
                if (sprite[neartagsprite].lotag == 4)
                {
                    activatehitag(sprite[neartagsprite].hitag);

                    j = sprite[neartagsprite].picnum;
                    if (j == SWITCH1ON)                     //1-time switch
                    {
                        sprite[neartagsprite].picnum = GIFTBOX;
                        sprite[neartagsprite].lotag = 0;
                        sprite[neartagsprite].hitag = 0;
                    }
                    if (j == GIFTBOX)                       //1-time switch
                    {
                        sprite[neartagsprite].picnum = SWITCH1ON;
                        sprite[neartagsprite].lotag = 0;
                        sprite[neartagsprite].hitag = 0;
                    }
                    if (j == SWITCH2ON) sprite[neartagsprite].picnum = SWITCH2OFF;
                    if (j == SWITCH2OFF) sprite[neartagsprite].picnum = SWITCH2ON;
                    if (j == SWITCH3ON) sprite[neartagsprite].picnum = SWITCH3OFF;
                    if (j == SWITCH3OFF) sprite[neartagsprite].picnum = SWITCH3ON;

                    dax = sprite[neartagsprite].x;
                    day = sprite[neartagsprite].y;
                    wsayfollow("switch.wav",4096L+(krand()&255)-128,256L,&dax,&day,0);
                }
            }
        }
    }

    if ((ssync[snum].bits & 2048) > 0)     // Shoot a bullet
    {
        if ((numbombs[snum] == 0) && (((ssync[snum].bits >> 13) & 7) == 2) && (myconnectindex == snum))
            locselectedgun = 0;
        if ((nummissiles[snum] == 0) && (((ssync[snum].bits >> 13) & 7) == 3) && (myconnectindex == snum))
            locselectedgun = 1;
        if ((numgrabbers[snum] == 0) && (((ssync[snum].bits >> 13) & 7) == 4) && (myconnectindex == snum))
            locselectedgun = 1;

        if ((health[snum] >= 0) || ((krand() & 127) > -health[snum]))
            switch ((ssync[snum].bits >> 13) & 7)
            {
            case 0:
                if (lockclock > lastchaingun[snum]+8)
                {
                    lastchaingun[snum] = lockclock;
                    shootgun(snum,&pos[snum],ang[snum],horiz[snum],cursectnum[snum],0);
                }
                break;
            case 1:
                if ((oflags[snum] & 2048) == 0)
                    shootgun(snum,&pos[snum],ang[snum],horiz[snum],cursectnum[snum],1);
                break;
            case 2:
                if ((oflags[snum] & 2048) == 0)
                    if (numbombs[snum] > 0)
                    {
                        shootgun(snum,&pos[snum],ang[snum],horiz[snum],cursectnum[snum],2);
                        changenumbombs(snum,-1);
                    }
                break;
            case 3:
                if ((oflags[snum] & 2048) == 0)
                    if (nummissiles[snum] > 0)
                    {
                        shootgun(snum,&pos[snum],ang[snum],horiz[snum],cursectnum[snum],3);
                        changenummissiles(snum,-1);
                    }
                break;
            case 4:
                if ((oflags[snum] & 2048) == 0)
                    if (numgrabbers[snum] > 0)
                    {
                        shootgun(snum,&pos[snum],ang[snum],horiz[snum],cursectnum[snum],4);
                        changenumgrabbers(snum,-1);
                    }
                break;
            }
    }

    if ((ssync[snum].bits&4096) > (oflags[snum]&4096))  //Keypad enter
    {
        dimensionmode[snum]++;
        if (dimensionmode[snum] > 3) dimensionmode[snum] = 1;
    }

    oflags[snum] = ssync[snum].bits;
}

void view(short snum, vec3_t *v, short *vsectnum, short ang, int horiz)
{
    spritetype *sp;
    int i, nx, ny, nz, hx, hy /*, hz*/;
    short bakcstat, daang;
    hitdata_t hitinfo;

    nx = (sintable[(ang+1536)&2047]>>4);
    ny = (sintable[(ang+1024)&2047]>>4);
    nz = (horiz-100)*128;

    sp = &sprite[snum];

    bakcstat = sp->cstat;
    sp->cstat &= (short)~0x101;

    updatesectorz(v->x,v->y,v->z,vsectnum);
    hitscan(v,*vsectnum,nx,ny,nz,&hitinfo,CLIPMASK1);
    hx = hitinfo.x-v->x; hy = hitinfo.y-v->y;
    if (klabs(nx)+klabs(ny) > klabs(hx)+klabs(hy))
    {
        *vsectnum = hitinfo.sect;
        if (hitinfo.wall >= 0)
        {
            daang = getangle(wall[wall[hitinfo.wall].point2].x-wall[hitinfo.wall].x,
                             wall[wall[hitinfo.wall].point2].y-wall[hitinfo.wall].y);

            i = nx*sintable[daang]+ny*sintable[(daang+1536)&2047];
            if (klabs(nx) > klabs(ny)) hx -= mulscale28(nx,i);
            else hy -= mulscale28(ny,i);
        }
        else if (hitinfo.sprite < 0)
        {
            if (klabs(nx) > klabs(ny)) hx -= (nx>>5);
            else hy -= (ny>>5);
        }
        if (klabs(nx) > klabs(ny)) i = divscale16(hx,nx);
        else i = divscale16(hy,ny);
        if (i < cameradist) cameradist = i;
    }
    v->x = v->x+mulscale16(nx,cameradist);
    v->y = v->y+mulscale16(ny,cameradist);
    v->z = v->z+mulscale16(nz,cameradist);

    updatesectorz(v->x,v->y,v->z,vsectnum);

    sp->cstat = bakcstat;
}

void drawscreen(short snum, int dasmoothratio)
{
    int i, j, k=0, l, charsperline, tempint;
    int x1, y1, x2, y2, ox1, oy1, ox2, oy2, dist, maxdist;
    vec3_t cpos;
    int choriz, czoom, tposx, tposy;
    int tiltlock, ovisibility, oparallaxvisibility;
    short cang, csect;
    fix16_t tang;
    char ch, *ptr, *ptr2, *ptr3, *ptr4;
    tspriteptr_t tspr;

    int32_t const viewingRange = viewingrange;

    smoothratio = max(min(dasmoothratio,65536),0);

    dointerpolations();

    if ((snum == myconnectindex) && ((networkmode == 1) || (myconnectindex != connecthead)))
    {
        cpos.x = omy.x+mulscale16(my.x-omy.x,smoothratio);
        cpos.y = omy.y+mulscale16(my.y-omy.y,smoothratio);
        cpos.z = omy.z+mulscale16(my.z-omy.z,smoothratio);
        choriz = omyhoriz+mulscale16(myhoriz-omyhoriz,smoothratio);
        cang = omyang+mulscale16((int)(((myang+1024-omyang)&2047)-1024),smoothratio);
    }
    else
    {
        cpos.x = opos[snum].x+mulscale16(pos[snum].x-opos[snum].x,smoothratio);
        cpos.y = opos[snum].y+mulscale16(pos[snum].y-opos[snum].y,smoothratio);
        cpos.z = opos[snum].z+mulscale16(pos[snum].z-opos[snum].z,smoothratio);
        choriz = ohoriz[snum]+mulscale16(horiz[snum]-ohoriz[snum],smoothratio);
        cang = oang[snum]+mulscale16(((ang[snum]+1024-oang[snum])&2047)-1024,smoothratio);
    }
    czoom = ozoom[snum]+mulscale16(zoom[snum]-ozoom[snum],smoothratio);

    setears(cpos.x,cpos.y,(int)sintable[(cang+512)&2047]<<14,(int)sintable[cang&2047]<<14);

    if (dimensionmode[myconnectindex] == 3)
    {
        tempint = screensize;

        if (((loc.bits&32) > (screensizeflag&32)) && (screensize > 64))
        {
            ox1 = ((xdim-screensize)>>1);
            ox2 = ox1+screensize-1;
            oy1 = (((ydim-32)-scale(screensize,ydim-32,xdim))>>1);
            oy2 = oy1 + scale(screensize,ydim-32,xdim)-1;
            screensize -= (screensize>>3);

            if (tempint > xdim)
            {
                screensize = xdim;

                renderFlushPerms();

                refreshstatusbar();
            }

            x1 = ((xdim-screensize)>>1);
            x2 = x1+screensize-1;
            y1 = (((ydim-32)-scale(screensize,ydim-32,xdim))>>1);
            y2 = y1 + scale(screensize,ydim-32,xdim)-1;
            videoSetViewableArea(x1,y1,x2,y2);

            // (ox1,oy1)⁄ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒø
            //          ≥  (x1,y1)        ≥
            //          ≥     ⁄ƒƒƒƒƒø     ≥
            //          ≥     ≥     ≥     ≥
            //          ≥     ¿ƒƒƒƒƒŸ     ≥
            //          ≥        (x2,y2)  ≥
            //          ¿ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŸ(ox2,oy2)

            drawtilebackground(/*0L,0L,*/ BACKGROUND,8,ox1,oy1,x1-1,oy2,0);
            drawtilebackground(/*0L,0L,*/ BACKGROUND,8,x2+1,oy1,ox2,oy2,0);
            drawtilebackground(/*0L,0L,*/ BACKGROUND,8,x1,oy1,x2,y1-1,0);
            drawtilebackground(/*0L,0L,*/ BACKGROUND,8,x1,y2+1,x2,oy2,0);
        }
        if (((loc.bits&16) > (screensizeflag&16)) && (screensize <= xdim))
        {
            screensize += (screensize>>3);
            if ((screensize > xdim) && (tempint == xdim))
            {
                screensize = xdim+1;
                x1 = 0; y1 = 0;
                x2 = xdim-1; y2 = ydim-1;
            }
            else
            {
                if (screensize > xdim) screensize = xdim;
                x1 = ((xdim-screensize)>>1);
                x2 = x1+screensize-1;
                y1 = (((ydim-32)-scale(screensize,ydim-32,xdim))>>1);
                y2 = y1 + scale(screensize,ydim-32,xdim)-1;
            }
            videoSetViewableArea(x1,y1,x2,y2);
        }
        screensizeflag = loc.bits;
    }

    if (dimensionmode[snum] != 2)
    {
        if (r_usenewaspect)
        {
            newaspect_enable = 1;
            videoSetCorrectedAspect();
        }

        if ((numplayers > 1) && (option[4] == 0))
        {
            //Do not draw other views constantly if they're staying still
            //It's a shame this trick will only work in screen-buffer mode
            //At least screen-buffer mode covers all the HI hi-res modes
            //if (vidoption == 2)
            //{
            for (i=connecthead; i>=0; i=connectpoint2[i]) frame2draw[i] = 0;
            frame2draw[snum] = 1;

            //2-1,3-1,4-2
            //5-2,6-2,7-2,8-3,9-3,10-3,11-3,12-4,13-4,14-4,15-4,16-5
            x1 = pos[snum].x; y1 = pos[snum].y;
            for (j=(numplayers>>2)+1; j>0; j--)
            {
                maxdist = 0x80000000;
                for (i=connecthead; i>=0; i=connectpoint2[i])
                    if (frame2draw[i] == 0)
                    {
                        x2 = pos[i].x-x1; y2 = pos[i].y-y1;
                        dist = dmulscale12(x2,x2,y2,y2);

                        if (dist < 64) dist = 16384;
                        else if (dist > 16384) dist = 64;
                        else dist = 1048576 / dist;

                        dist *= frameskipcnt[i];

                        //Increase frame rate if screen is moving
                        if ((pos[i].x != opos[i].x) || (pos[i].y != opos[i].y) ||
                            (pos[i].z != opos[i].z) || (ang[i] != oang[i]) ||
                            (horiz[i] != ohoriz[i])) dist += dist;

                        if (dist > maxdist) maxdist = dist, k = i;
                    }

                for (i=connecthead; i>=0; i=connectpoint2[i])
                    frameskipcnt[i] += (frameskipcnt[i]>>3)+1;
                frameskipcnt[k] = 0;

                frame2draw[k] = 1;
            }
            //}
            //else
            //{
            //   for(i=connecthead;i>=0;i=connectpoint2[i]) frame2draw[i] = 1;
            //}

            for (i=connecthead,j=0; i>=0; i=connectpoint2[i],j++)
                if (frame2draw[i] != 0)
                {
                    if (numplayers <= 4)
                    {
                        switch (j)
                        {
                        case 0: videoSetViewableArea(0,0,(xdim>>1)-1,(ydim>>1)-1); break;
                        case 1: videoSetViewableArea((xdim>>1),0,xdim-1,(ydim>>1)-1); break;
                        case 2: videoSetViewableArea(0,(ydim>>1),(xdim>>1)-1,ydim-1); break;
                        case 3: videoSetViewableArea((xdim>>1),(ydim>>1),xdim-1,ydim-1); break;
                        }
                    }
                    else
                    {
                        switch (j)
                        {
                        case 0: videoSetViewableArea(0,0,(xdim>>2)-1,(ydim>>2)-1); break;
                        case 1: videoSetViewableArea(xdim>>2,0,(xdim>>1)-1,(ydim>>2)-1); break;
                        case 2: videoSetViewableArea(xdim>>1,0,xdim-(xdim>>2)-1,(ydim>>2)-1); break;
                        case 3: videoSetViewableArea(xdim-(xdim>>2),0,xdim-1,(ydim>>2)-1); break;
                        case 4: videoSetViewableArea(0,ydim>>2,(xdim>>2)-1,(ydim>>1)-1); break;
                        case 5: videoSetViewableArea(xdim>>2,ydim>>2,(xdim>>1)-1,(ydim>>1)-1); break;
                        case 6: videoSetViewableArea(xdim>>1,ydim>>2,xdim-(xdim>>2)-1,(ydim>>1)-1); break;
                        case 7: videoSetViewableArea(xdim-(xdim>>2),ydim>>2,xdim-1,(ydim>>1)-1); break;
                        case 8: videoSetViewableArea(0,ydim>>1,(xdim>>2)-1,ydim-(ydim>>2)-1); break;
                        case 9: videoSetViewableArea(xdim>>2,ydim>>1,(xdim>>1)-1,ydim-(ydim>>2)-1); break;
                        case 10: videoSetViewableArea(xdim>>1,ydim>>1,xdim-(xdim>>2)-1,ydim-(ydim>>2)-1); break;
                        case 11: videoSetViewableArea(xdim-(xdim>>2),ydim>>1,xdim-1,ydim-(ydim>>2)-1); break;
                        case 12: videoSetViewableArea(0,ydim-(ydim>>2),(xdim>>2)-1,ydim-1); break;
                        case 13: videoSetViewableArea(xdim>>2,ydim-(ydim>>2),(xdim>>1)-1,ydim-1); break;
                        case 14: videoSetViewableArea(xdim>>1,ydim-(ydim>>2),xdim-(xdim>>2)-1,ydim-1); break;
                        case 15: videoSetViewableArea(xdim-(xdim>>2),ydim-(ydim>>2),xdim-1,ydim-1); break;
                        }
                    }

                    if (i == snum)
                    {
                        sprite[playersprite[snum]].cstat |= 0x8000;
                        drawrooms(cpos.x,cpos.y,cpos.z,cang,choriz,cursectnum[i]);
                        sprite[playersprite[snum]].cstat &= (uint16_t) ~0x8000;
                        analyzesprites(cpos.x,cpos.y);
                    }
                    else
                    {
                        sprite[playersprite[i]].cstat |= 0x8000;
                        drawrooms(pos[i].x,pos[i].y,pos[i].z,ang[i],horiz[i],cursectnum[i]);
                        sprite[playersprite[i]].cstat &= (uint16_t) ~0x8000;
                        analyzesprites(pos[i].x,pos[i].y);
                    }
                    renderDrawMasks();
                    if ((numgrabbers[i] > 0) || (nummissiles[i] > 0) || (numbombs[i] > 0))
                        rotatesprite(160<<16,184L<<16,65536,0,GUNONBOTTOM,sector[cursectnum[i]].floorshade,0,2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);

                    if (lockclock < 384)
                    {
                        if (lockclock < 128)
                            rotatesprite(320<<15,200<<15,lockclock<<9,lockclock<<4,DEMOSIGN,(128-lockclock)>>2,0,1+2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
                        else if (lockclock < 256)
                            rotatesprite(320<<15,200<<15,65536,0,DEMOSIGN,0,0,2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
                        else
                            rotatesprite(320<<15,200<<15,(384-lockclock)<<9,lockclock<<4,DEMOSIGN,(lockclock-256)>>2,0,1+2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
                    }

                    if (health[i] <= 0)
                        rotatesprite(320<<15,200<<15,(-health[i])<<11,(-health[i])<<5,NO,0,0,2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
                }
        }
        else
        {
            //Init for screen rotation
            if (videoGetRenderMode() == 0)     // JBF 20031220
            {
                tiltlock = screentilt;
                if ((tiltlock) || (detailmode))
                {
                    walock[TILE_TILT] = 255;
                    if (waloff[TILE_TILT] == 0)
                        g_cache.allocateBlock(&waloff[TILE_TILT],320L*320L,&walock[TILE_TILT]);
                    if ((tiltlock&1023) == 0)
                        renderSetTarget(TILE_TILT,200L>>detailmode,320L>>detailmode);
                    else
                        renderSetTarget(TILE_TILT,320L>>detailmode,320L>>detailmode);
                    if ((tiltlock&1023) == 512)
                    {
                        //Block off unscreen section of 90¯ tilted screen
                        j = ((320-60)>>detailmode);
                        for (i=(60>>detailmode)-1; i>=0; i--)
                        {
                            startumost[i] = 1; startumost[i+j] = 1;
                            startdmost[i] = 0; startdmost[i+j] = 0;
                        }
                    }

                    i = (tiltlock&511); if (i > 256) i = 512-i;
                    i = sintable[i+512]*8 + sintable[i]*5L;
                    renderSetAspect(i>>1,yxaspect);
                }
            }
            else
            {
                tiltlock = screentilt;
                // Ken loves to interpolate
#ifdef USE_OPENGL
                renderSetRollAngle(oscreentilt + mulscale16(((screentilt-oscreentilt+1024)&2047)-1024,smoothratio));
#endif
            }

            if (bitmap_test(gotpic, FLOORMIRROR))
            {
                dist = 0x7fffffff; i = 0;
                for (k=floormirrorcnt-1; k>=0; k--)
                {
                    j = klabs(wall[sector[floormirrorsector[k]].wallptr].x-cpos.x);
                    j += klabs(wall[sector[floormirrorsector[k]].wallptr].y-cpos.y);
                    if (j < dist) dist = j, i = k;
                }

                //if (cpos.z > sector[floormirrorsector[i]].ceilingz) i = 1-i; //SOS

                j = floormirrorsector[i];

                if (cameradist < 0) sprite[playersprite[snum]].cstat |= 0x8000;
                drawrooms(cpos.x,cpos.y,(sector[j].floorz<<1)-cpos.z,cang,201-choriz,j); //SOS
                //drawrooms(cpos.x,cpos.y,cpos.z,cang,choriz,j+MAXSECTORS); //SOS
                sprite[playersprite[snum]].cstat &= (uint16_t) ~0x8000;
                analyzesprites(cpos.x,cpos.y);
                renderDrawMasks();

                //Temp horizon
                if (videoGetRenderMode() == 0)
                {
                    l = scale(choriz-100,windowxy2.x-windowxy1.x,320)+((windowxy1.y+windowxy2.y)>>1);
                    videoBeginDrawing();   //{{{
                    for (y1=windowxy1.y,y2=windowxy2.y; y1<y2; y1++,y2--)
                    {
                        ptr = (char *)(frameplace+ylookup[y1]);
                        ptr2 = (char *)(frameplace+ylookup[y2]);
                        ptr3 = palookup[18];
                        ptr3 += (min(klabs(y1-l)>>2,31)<<8);
                        ptr4 = palookup[18];
                        ptr4 += (min(klabs(y2-l)>>2,31)<<8);

                        j = sintable[((y2+(int32_t) totalclock)<<6)&2047];
                        j += sintable[((y2-(int32_t) totalclock)<<7)&2047];
                        j >>= 14;

                        //ptr2 += j;

                        //for(x1=windowxy1.x;x1<=windowxy2.x;x1++)
                        //	{ ch = ptr[x1]; ptr[x1] = ptr3[ptr2[x1]]; ptr2[x1] = ptr4[ch]; }

                        ox1 = windowxy1.x-min(j,0);
                        ox2 = windowxy2.x-max(j,0);

                        for (x1=windowxy1.x; x1<ox1; x1++)
                        { ch = ptr[x1]; ptr[x1] = ptr3[ptr2[x1]]; ptr2[x1] = ptr4[ch]; }
                        for (x1=ox2+1; x1<=windowxy2.x; x1++)
                        { ch = ptr[x1]; ptr[x1] = ptr3[ptr2[x1]]; ptr2[x1] = ptr4[ch]; }

                        ptr2 += j;
                        for (x1=ox1; x1<=ox2; x1++)
                        { ch = ptr[x1]; ptr[x1] = ptr3[ptr2[x1]]; ptr2[x1] = ptr4[ch]; }
                    }
                    videoEndDrawing(); //}}}
                }
                bitmap_clear(gotpic, FLOORMIRROR);
            }


            //Over the shoulder mode
            csect = cursectnum[snum];
            if (cameradist >= 0)
            {
                cang += cameraang;
                view(playersprite[snum],&cpos,&csect,cang,choriz);
            }

            EDUKE32_STATIC_ASSERT((MIRRORLABEL & 31) == 0);
            EDUKE32_STATIC_ASSERT(MAXMIRRORS == 64);
            auto const gotpicptr = (uint32_t const *)&gotpic[MIRRORLABEL>>3];
            auto const gotmirrors = gotpicptr[0] | gotpicptr[1];

            if (gotmirrors)
                for (i=MAXMIRRORS-1; i>=0; i--)
                    if (bitmap_test(gotpic, i+MIRRORLABEL))
                    {
                        bitmap_clear(gotpic, i+MIRRORLABEL);

                        //Prepare drawrooms for drawing mirror and calculate reflected
                        //position into tposx, tposy, and tang (tpos.z == cpos.z)
                        //Must call renderPrepareMirror before drawrooms and
                        //          renderCompleteMirror after drawrooms
                        renderPrepareMirror(cpos.x,cpos.y,cpos.z,fix16_from_int(cang),fix16_from_int(choriz),
                                      mirrorwall[i],/*mirrorsector[i],*/ &tposx,&tposy,&tang);

                        ovisibility = g_visibility;
                        oparallaxvisibility = parallaxvisibility;
                        g_visibility <<= 1;
                        parallaxvisibility <<= 1;
                        ptr = palookup[0]; palookup[0] = palookup[17]; palookup[17] = ptr;

                        drawrooms(tposx,tposy,cpos.z,fix16_to_int(tang),choriz,mirrorsector[i]|MAXSECTORS);
                        for (j=0,tspr=&tsprite[0]; j<spritesortcnt; j++,tspr++)
                            if ((tspr->cstat & CSTAT_SPRITE_ALIGNMENT) == CSTAT_SPRITE_ALIGNMENT_FACING)
                                tspr->cstat |= 4;
                        analyzesprites(tposx,tposy);
                        renderDrawMasks();

                        ptr = palookup[0]; palookup[0] = palookup[17]; palookup[17] = ptr;
                        g_visibility = ovisibility;
                        parallaxvisibility = oparallaxvisibility;

                        renderCompleteMirror();   //Reverse screen x-wise in this function

                        break;
                    }

            if (cameradist < 0) sprite[playersprite[snum]].cstat |= 0x8000;
            drawrooms(cpos.x,cpos.y,cpos.z,cang,choriz,csect);
            sprite[playersprite[snum]].cstat &= (uint16_t) ~0x8000;
            analyzesprites(cpos.x,cpos.y);
            renderDrawMasks();

            //Finish for screen rotation
            if (videoGetRenderMode() == 0)        // JBF 20031220
            {
                if ((tiltlock) || (detailmode))
                {
                    renderRestoreTarget();
                    i = (tiltlock&511); if (i > 256) i = 512-i;
                    i = sintable[i+512]*8 + sintable[i]*5L;
                    if (detailmode == 0) i >>= 1;
                    rotatesprite(320<<15,200<<15,i,tiltlock+512,TILE_TILT,0,0,2+4+64,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
                    walock[TILE_TILT] = 1;
                }
            }

            if (((numgrabbers[screenpeek] > 0) || (nummissiles[screenpeek] > 0) || (numbombs[screenpeek] > 0)) && (cameradist < 0))
            {
                //Reset startdmost to bottom of screen
                if ((windowxy1.x == 0) && (windowxy2.x == 319) && (yxaspect == 65536) && (tiltlock == 0))
                {
                    x1 = 160L-(tilesiz[GUNONBOTTOM].x>>1); y1 = windowxy2.y+1;
                    for (i=0; i<tilesiz[GUNONBOTTOM].x; i++)
                        startdmost[i+x1] = y1;
                }
                rotatesprite(160<<16,184L<<16,65536,0,GUNONBOTTOM,sector[cursectnum[screenpeek]].floorshade,0,2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
            }

#if 0
            if (cachecount != 0)
            {
                rotatesprite((320-16)<<16,16<<16,32768,0,BUILDDISK,0,0,2+64,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
                cachecount = 0;
            }
#endif

            if (lockclock < 384)
            {
                if (lockclock < 128)
                    rotatesprite(320<<15,200<<15,lockclock<<9,lockclock<<4,DEMOSIGN,(128-lockclock)>>2,0,1+2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
                else if (lockclock < 256)
                    rotatesprite(320<<15,200<<15,65536,0,DEMOSIGN,0,0,2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
                else
                    rotatesprite(320<<15,200<<15,(384-lockclock)<<9,lockclock<<4,DEMOSIGN,(lockclock-256)>>2,0,1+2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
            }

            if (health[screenpeek] <= 0)
                rotatesprite(320<<15,200<<15,(-health[screenpeek])<<11,(-health[screenpeek])<<5,NO,0,0,2,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
        }

        if (r_usenewaspect)
        {
            newaspect_enable = 0;
            renderSetAspect(viewingRange, tabledivide32_noinline(65536 * ydim * 8, xdim * 5));
        }
    }

    //Only animate lava if its picnum is on screen
    //gotpic is a bit array where the tile number's bit is set
    //whenever it is drawn (ceilings, walls, sprites, etc.)
    if (bitmap_test(gotpic, SLIME))
    {
        bitmap_clear(gotpic, SLIME);
        if (waloff[SLIME] != 0)
        {
            movelava((char *)waloff[SLIME]);
            tileInvalidate(SLIME,0,1);   // JBF 20031228
        }
    }

    if (!bitmap_test(show2dsector, cursectnum[snum]))
        searchmap(cursectnum[snum]);

    if (dimensionmode[snum] != 3)
    {
        //Move back pivot point
        i = scale(czoom,screensize,320);
        if (dimensionmode[snum] == 2)
        {
            videoClearViewableArea(0L);  //Clear screen to specified color
#ifdef USE_OPENGL
            // Don't refresh with split screen
            if ((videoGetRenderMode() >= REND_POLYMOST) && ((option[4] != 0) || (numplayers < 2)))
                refreshstatusbar(); // Necessary GL fills the entire screen with black
#endif
            renderDrawMapView(cpos.x,cpos.y,i,cang);
        }
        drawoverheadmap(cpos.x,cpos.y,i,cang);
    }

    if (typemode != 0)
    {
        charsperline = 40;
        //if (dimensionmode[snum] == 2) charsperline = 80;

        for (i=0; i<=typemessageleng; i+=charsperline)
        {
            for (j=0; j<charsperline; j++)
                tempbuf[j] = typemessage[i+j];
            if (typemessageleng < i+charsperline)
            {
                tempbuf[(typemessageleng-i)] = '_';
                tempbuf[(typemessageleng-i)+1] = 0;
            }
            else
                tempbuf[charsperline] = 0;
            //if (dimensionmode[snum] == 3)
            printext256(0L,(i/charsperline)<<3,31 /*183*/,-1,(char *)tempbuf,0);
            //else
            //   printext16(0L,((i/charsperline)<<3)+(pageoffset/640),10,-1,(char *)tempbuf,0);
        }
    }

    if (getmessageleng > 0)
    {
        charsperline = 40;
        //if (dimensionmode[snum] == 2) charsperline = 80;

        for (i=0; i<=getmessageleng; i+=charsperline)
        {
            for (j=0; j<charsperline; j++)
                tempbuf[j] = getmessage[i+j];
            if (getmessageleng < i+charsperline)
                tempbuf[(getmessageleng-i)] = 0;
            else
                tempbuf[charsperline] = 0;

            printext256(0L,((i/charsperline)<<3)+(ydim-32-8)-(((getmessageleng-1)/charsperline)<<3),31 /*151*/,-1,(char *)tempbuf,0);
        }
        if (totalclock > getmessagetimeoff)
            getmessageleng = 0;
    }
    if ((numplayers >= 2) && (screenpeek != myconnectindex))
    {
        j = 1;
        for (i=connecthead; i>=0; i=connectpoint2[i])
        {
            if (i == screenpeek) break;
            j++;
        }
        Bsprintf((char *)tempbuf,"(Player %d's view)",j);
        printext256((xdim>>1)-(Bstrlen((char *)tempbuf)<<2),0,24,-1,(char *)tempbuf,0);
    }

    if (syncstat != 0) printext256(68L,84L,31,0,"OUT OF SYNC!",0);
    if (syncstate != 0) printext256(68L,92L,31,0,"Missed Network packet!",0);

//   //Uncomment this to test cache locks
//extern int cacnum;
//typedef struct { int *hand, leng; char *lock; } cactype;
//extern cactype cac[];
//
//   j = 0;
//   for(i=0;i<cacnum;i++)
//      if ((*cac[i].lock) >= 200)
//      {
//         Bsprintf(tempbuf,"Locked- %ld: Leng:%ld, Lock:%ld",i,cac[i].leng,*cac[i].lock);
//         printext256(0L,j,31,-1,tempbuf,1); j += 6;
//      }

    videoNextPage();   // send completed frame to display

    while (totalclock >= ototalclock+(TIMERINTSPERSECOND/MOVESPERSECOND))
        faketimerhandler();

    if (keystatus[0x3f])   //F5
    {
        keystatus[0x3f] = 0;
        detailmode ^= 1;
        //videoSetRenderMode(3);
    }
    if (keystatus[0x58])   //F12
    {
        keystatus[0x58] = 0;
        videoCaptureScreen("captxxxx.tga",keystatus[0x2a]|keystatus[0x36]);
    }
    if (keystatus[0x3e])  //F4 - screen re-size
    {
        keystatus[0x3e] = 0;

        if (keystatus[0x2a]|keystatus[0x36])
        {
            videoSetGameMode(!fullscreen, xdim, ydim, bpp, upscalefactor);
        }
        else
        {

            //cycle through all modes
            j=-1;

            // work out a mask to select the mode
            for (i=0; i<validmodecnt; i++)
                if ((validmode[i].xdim == xdim) &&
                    (validmode[i].ydim == ydim) &&
                    (validmode[i].fs == fullscreen) &&
                    (validmode[i].bpp == bpp))
                { j=i; break; }

            for (k=0; k<validmodecnt; k++)
                if (validmode[k].fs == fullscreen && validmode[k].bpp == bpp) break;

            if (j==-1) j=k;
            else
            {
                j++;
                if (j==validmodecnt) j=k;
            }
            videoSetGameMode(fullscreen, validmode[j].xdim, validmode[j].ydim, bpp, upscalefactor);
        }
        screensize = xdim+1;

        Bsprintf(getmessage,"Video mode: %d x %d",xdim,ydim);
        getmessageleng = Bstrlen(getmessage);
        getmessagetimeoff = (int32_t) totalclock+120*5;
    }
    if (keystatus[0x57])  //F11 - brightness
    {
        keystatus[0x57] = 0;
        brightness++;
        if (brightness > 8) brightness = 0;
        videoSetPalette(brightness,0,0);
    }

    if (option[4] == 0)           //Single player only keys
    {
        if (keystatus[0xd2])   //Insert - Insert player
        {
            keystatus[0xd2] = 0;
            if (numplayers < MAXPLAYERS)
            {
                connectpoint2[numplayers-1] = numplayers;
                connectpoint2[numplayers] = -1;

                movefifoend[numplayers] = movefifoend[0];   //HACK 01/05/2000

                initplayersprite(numplayers);

                videoClearScreen(0L);  //Clear screen to specified color

                numplayers++;
            }
        }
        if (keystatus[0xd3])   //Delete - Delete player
        {
            keystatus[0xd3] = 0;
            if (numplayers > 1)
            {
                numplayers--;
                connectpoint2[numplayers-1] = -1;

                deletesprite(playersprite[numplayers]);
                playersprite[numplayers] = -1;

                if (myconnectindex >= numplayers) myconnectindex = 0;
                if (screenpeek >= numplayers) screenpeek = 0;

                if (numplayers < 2)
                    setup3dscreen();
                else
                    videoClearScreen(0L);  //Clear screen to specified color
            }
        }
        if (keystatus[0x46])   //Scroll Lock
        {
            keystatus[0x46] = 0;

            myconnectindex = connectpoint2[myconnectindex];
            if (myconnectindex < 0) myconnectindex = connecthead;
            screenpeek = myconnectindex;
        }
    }

    restoreinterpolations();
}

void movethings(void)
{
    int i;

    gotlastpacketclock = (int32_t) totalclock;
    for (i=connecthead; i>=0; i=connectpoint2[i])
    {
        copybufbyte(&ffsync[i],&baksync[movefifoend[i]][i],sizeof(input));
        movefifoend[i] = ((movefifoend[i]+1)&(MOVEFIFOSIZ-1));
    }
}

void fakedomovethings(void)
{
    input *syn;
    int /*i, j, k,*/ doubvel, xvect, yvect, goalz;
    short bakcstat;

    syn = (input *)&baksync[fakemovefifoplc][myconnectindex];

    omy = my;
    omyang = myang;
    omyhoriz = myhoriz;

    bakcstat = sprite[playersprite[myconnectindex]].cstat;
    sprite[playersprite[myconnectindex]].cstat &= ~0x101;

    if ((syn->fvel|syn->svel) != 0)
    {
        doubvel = TICSPERFRAME << ((syn->bits&256) >> 7);

        xvect = 0, yvect = 0;
        if (syn->fvel != 0)
        {
            xvect += ((((int)syn->fvel)*doubvel*(int)sintable[(myang+512)&2047])>>3);
            yvect += ((((int)syn->fvel)*doubvel*(int)sintable[myang&2047])>>3);
        }
        if (syn->svel != 0)
        {
            xvect += ((((int)syn->svel)*doubvel*(int)sintable[myang&2047])>>3);
            yvect += ((((int)syn->svel)*doubvel*(int)sintable[(myang+1536)&2047])>>3);
        }
        if (flytime[myconnectindex] > lockclock) { xvect += xvect; yvect += yvect; }   // DOuble flying speed
        clipmove(&my,&mycursectnum,xvect,yvect,128L,4<<8,4<<8,CLIPMASK0);
    }

    pushmove(&my,&mycursectnum,128L,4<<8,4<<8,CLIPMASK0);
    getzrange(&my,mycursectnum,&globhiz,&globhihit,&globloz,&globlohit,128L,CLIPMASK0);

    if (syn->avel != 0)          //ang += avel * constant
    {
        //ENGINE calculates avel for you
        doubvel = TICSPERFRAME;
        if ((syn->bits&256) > 0)  //Lt. shift makes turn velocity 50% faster
            doubvel += (TICSPERFRAME>>1);
        myang += ((((int)syn->avel)*doubvel)>>4);
        myang &= 2047;
    }

    if (((syn->bits&8) > 0) && (myhoriz > 100-(200>>1))) myhoriz -= 4;   //-
    if (((syn->bits&4) > 0) && (myhoriz < 100+(200>>1))) myhoriz += 4;   //+

    goalz = globloz-EYEHEIGHT;
    if (sector[mycursectnum].lotag == 4)   //slime sector
        if ((globlohit&0xc000) != 49152)            //You're not on a sprite
        {
            goalz = globloz-(8<<8);
            if (my.z >= goalz-(2<<8))
                clipmove(&my,&mycursectnum,-(TICSPERFRAME<<14),-(TICSPERFRAME<<14),128L,4<<8,4<<8,CLIPMASK0);
        }
    if (goalz < globhiz+(16<<8))   //ceiling&floor too close
        goalz = ((globloz+globhiz)>>1);

    if (health[myconnectindex] >= 0)
    {
        if ((syn->bits&1) > 0)                         //A (stand high)
        {
            if (flytime[myconnectindex] <= lockclock)
            {
                if (my.z >= globloz-(32<<8))
                {
                    goalz -= (16<<8);
                    if (syn->bits&256) goalz -= (24<<8);
                }
            }
            else
            {
                myzvel -= 192;
                if (syn->bits&256) myzvel -= 192;
            }
        }
        if ((syn->bits&2) > 0)                         //Z (stand low)
        {
            if (flytime[myconnectindex] <= lockclock)
            {
                goalz += (12<<8);
                if (syn->bits&256) goalz += (12<<8);
            }
            else
            {
                myzvel += 192;
                if (syn->bits&256) myzvel += 192;
            }
        }
    }

    if (flytime[myconnectindex] <= lockclock)
    {
        if (my.z < goalz)
            myzvel += (TICSPERFRAME<<4);
        else
            myzvel = (((goalz-my.z)*TICSPERFRAME)>>5);
    }
    else
    {
        myzvel -= (myzvel>>2);
        myzvel -= ksgn(myzvel);
    }

    my.z += myzvel;
    if (my.z > globloz-(4<<8)) my.z = globloz-(4<<8), myzvel = 0;
    if (my.z < globhiz+(4<<8)) my.z = globhiz+(4<<8), myzvel = 0;

    sprite[playersprite[myconnectindex]].cstat = bakcstat;

    mybak[fakemovefifoplc] = my;
    myangbak[fakemovefifoplc] = myang;
    myhorizbak[fakemovefifoplc] = myhoriz;
    fakemovefifoplc = (fakemovefifoplc+1)&(MOVEFIFOSIZ-1);
}

//Prediction correction
void fakedomovethingscorrect(void)
{
    int i;

    if ((networkmode == 0) && (myconnectindex == connecthead)) return;

    i = ((movefifoplc-1)&(MOVEFIFOSIZ-1));

    if ((pos[myconnectindex].x == mybak[i].x) &&
        (pos[myconnectindex].y == mybak[i].y) &&
        (pos[myconnectindex].z == mybak[i].z) &&
        (horiz[myconnectindex] == myhorizbak[i]) &&
        (ang[myconnectindex] == myangbak[i]))
        return;

    //Re-start fakedomovethings back to place of error
    my = omy = pos[myconnectindex];
    myzvel = hvel[myconnectindex];
    myang = omyang = ang[myconnectindex];
    mycursectnum = cursectnum[myconnectindex];
    myhoriz = omyhoriz = horiz[myconnectindex];

    fakemovefifoplc = movefifoplc;
    while (fakemovefifoplc != movefifoend[myconnectindex]) fakedomovethings();
}

void domovethings(void)
{
    short i, j, startwall, endwall;
    // spritetype *spr;
    walltype *wal;
    // vec3_t *ospr;

    nummoves++;

    for (i=connecthead; i>=0; i=connectpoint2[i])
        copybufbyte(&baksync[movefifoplc][i],&ssync[i],sizeof(input));
    movefifoplc = ((movefifoplc+1)&(MOVEFIFOSIZ-1));

    if (option[4] != 0)
    {
        syncval[syncvalhead] = (char)(randomseed&255);
        syncvalhead = ((syncvalhead+1)&(MOVEFIFOSIZ-1));
    }

    for (i=connecthead; i>=0; i=connectpoint2[i])
    {
        opos[i] = pos[i];
        ohoriz[i] = horiz[i];
        ozoom[i] = zoom[i];
        oang[i] = ang[i];
    }

    for (i=NUMSTATS-1; i>=0; i--)
        if (statrate[i] >= 0)
            for (j=headspritestat[i]; j>=0; j=nextspritestat[j])
                if (((nummoves-j)&statrate[i]) == 0)
                    copybuf(&sprite[j].x,&osprite[j].x,3);

    for (i=connecthead; i>=0; i=connectpoint2[i])
        ocursectnum[i] = cursectnum[i];

    updateinterpolations();

    if ((numplayers <= 2) && (recstat == 1))
    {
        j = 0;
        for (i=connecthead; i>=0; i=connectpoint2[i])
        {
            copybufbyte(&ssync[i],&recsync[reccnt][j],sizeof(input));
            j++;
        }
        reccnt++; if (reccnt > 16383) reccnt = 16383;
    }

    lockclock += TICSPERFRAME;
    drawstatusflytime(screenpeek);   // Andy did this

    if (cameradist >= 0)
    {
        cameradist = min(cameradist+(((int32_t) totalclock-cameraclock)<<10),65536);
        if (keystatus[0x52])       //0
            cameraang -= (((int32_t) totalclock-cameraclock)<<(2+(keystatus[0x2a]|keystatus[0x36])));
        if (keystatus[0x53])       //.
            cameraang += (((int32_t) totalclock-cameraclock)<<(2+(keystatus[0x2a]|keystatus[0x36])));
        cameraclock = (int32_t) totalclock;
    }

    for (i=connecthead; i>=0; i=connectpoint2[i])
    {
        processinput(i);                        //Move player

        checktouchsprite(i,cursectnum[i]);      //Pick up coins
        startwall = sector[cursectnum[i]].wallptr;
        endwall = startwall + sector[cursectnum[i]].wallnum;
        for (j=startwall,wal=&wall[j]; j<endwall; j++,wal++)
            if (wal->nextsector >= 0) checktouchsprite(i,wal->nextsector);
    }

    doanimations();
    tagcode();            //Door code, moving sector code, other stuff
    statuslistcode();     //Monster / bullet code / explosions

    fakedomovethingscorrect();

    checkmasterslaveswitch();
}

void getinput(void)
{
    char ch /*, keystate, *ptr*/;
    int i, j /*, k*/;
    int mousx, mousy, bstatus;

    if (typemode == 0)           //if normal game keys active
    {
        if (keystatus[keys[15]])
        {
            keystatus[keys[15]] = 0;

            screenpeek = connectpoint2[screenpeek];
            if (screenpeek < 0) screenpeek = connecthead;
            drawstatusbar(screenpeek);   // Andy did this
        }

        for (i=7; i>=0; i--)
            if (keystatus[i+2])
            { keystatus[i+2] = 0; locselectedgun = i; break; }
    }


    //KEYTIMERSTUFF
    if (!keystatus[keys[5]])
    {
        if (keystatus[keys[2]]) avel = max(avel-16*TICSPERFRAME,-128);
        if (keystatus[keys[3]]) avel = min(avel+16*TICSPERFRAME,127);
    }
    else
    {
        if (keystatus[keys[2]]) svel = min(svel+8*TICSPERFRAME,127);
        if (keystatus[keys[3]]) svel = max(svel-8*TICSPERFRAME,-128);
    }
    if (keystatus[keys[0]]) fvel = min(fvel+8*TICSPERFRAME,127);
    if (keystatus[keys[1]]) fvel = max(fvel-8*TICSPERFRAME,-128);
    if (keystatus[keys[12]]) svel = min(svel+8*TICSPERFRAME,127);
    if (keystatus[keys[13]]) svel = max(svel-8*TICSPERFRAME,-128);

    if (avel < 0) avel = min(avel+12*TICSPERFRAME,0);
    if (avel > 0) avel = max(avel-12*TICSPERFRAME,0);
    if (svel < 0) svel = min(svel+2*TICSPERFRAME,0);
    if (svel > 0) svel = max(svel-2*TICSPERFRAME,0);
    if (fvel < 0) fvel = min(fvel+2*TICSPERFRAME,0);
    if (fvel > 0) fvel = max(fvel-2*TICSPERFRAME,0);

    if ((option[4] == 0) && (numplayers >= 2))
    {
        if (!keystatus[0x4f])
        {
            if (keystatus[0x4b]) avel2 = max(avel2-16*TICSPERFRAME,-128);
            if (keystatus[0x4d]) avel2 = min(avel2+16*TICSPERFRAME,127);
        }
        else
        {
            if (keystatus[0x4b]) svel2 = min(svel2+8*TICSPERFRAME,127);
            if (keystatus[0x4d]) svel2 = max(svel2-8*TICSPERFRAME,-128);
        }
        if (keystatus[0x48]) fvel2 = min(fvel2+8*TICSPERFRAME,127);
        if (keystatus[0x4c]) fvel2 = max(fvel2-8*TICSPERFRAME,-128);

        if (avel2 < 0) avel2 = min(avel2+12*TICSPERFRAME,0);
        if (avel2 > 0) avel2 = max(avel2-12*TICSPERFRAME,0);
        if (svel2 < 0) svel2 = min(svel2+2*TICSPERFRAME,0);
        if (svel2 > 0) svel2 = max(svel2-2*TICSPERFRAME,0);
        if (fvel2 < 0) fvel2 = min(fvel2+2*TICSPERFRAME,0);
        if (fvel2 > 0) fvel2 = max(fvel2-2*TICSPERFRAME,0);
    }

    oscreentilt = screentilt;
    if (keystatus[0x1a]) screentilt += ((4*TICSPERFRAME)<<(keystatus[0x2a]|keystatus[0x36]));
    if (keystatus[0x1b]) screentilt -= ((4*TICSPERFRAME)<<(keystatus[0x2a]|keystatus[0x36]));

    i = (TICSPERFRAME<<1);
    while ((screentilt != 0) && (i > 0))
    { screentilt = ((screentilt+ksgn(screentilt-1024))&2047); i--; }
    if (keystatus[0x28]) screentilt = 1536;


    loc.fvel = min(max(fvel,-128+8),127-8);
    loc.svel = min(max(svel,-128+8),127-8);
    loc.avel = min(max(avel,-128+16),127-16);

    mouseGetValues(&mousx,&mousy,&bstatus);
    loc.avel = min(max(loc.avel+(mousx<<3),-128),127);
    loc.fvel = min(max(loc.fvel-(mousy<<3),-128),127);

    loc.bits = (locselectedgun<<13);
    if (typemode == 0)           //if normal game keys active
    {
        loc.bits |= (keystatus[0x32]<<9);                 //M (be master)
        loc.bits |= ((keystatus[keys[14]]==1)<<12);       //Map mode
    }
    loc.bits |= keystatus[keys[8]];                   //Stand high
    loc.bits |= (keystatus[keys[9]]<<1);              //Stand low
    loc.bits |= (keystatus[keys[16]]<<4);             //Zoom in
    loc.bits |= (keystatus[keys[17]]<<5);             //Zoom out
    loc.bits |= (keystatus[keys[4]]<<8);                 //Run
    loc.bits |= (keystatus[keys[10]]<<2);                //Look up
    loc.bits |= (keystatus[keys[11]]<<3);                //Look down
    loc.bits |= ((keystatus[keys[7]]==1)<<10);           //Space
    loc.bits |= ((keystatus[keys[6]]==1)<<11);           //Shoot
    loc.bits |= (((bstatus&6)>(oldmousebstatus&6))<<10); //Space
    loc.bits |= (((bstatus&1)>(oldmousebstatus&1))<<11); //Shoot

    oldmousebstatus = bstatus;
    if (((loc.bits&2048) > 0) && (locselectedgun == 0))
        oldmousebstatus &= ~1;     //Allow continous fire with mouse for chain gun

    //PRIVATE KEYS:
#if 0
    if (keystatus[0xb7])  //Printscreen
    {
        keystatus[0xb7] = 0;
        printscreeninterrupt();
    }
#endif
    if (keystatus[0x2f])       //V
    {
        keystatus[0x2f] = 0;
        if (cameradist < 0) cameradist = 0; else cameradist = -1;
        cameraang = 0;
    }

    if (typemode == 0)           //if normal game keys active
    {
        if (keystatus[0x19])  //P
        {
            keystatus[0x19] = 0;
            parallaxtype++;
            if (parallaxtype > 2) parallaxtype = 0;
        }
        if (keystatus[0x38]|keystatus[0xb8])  //ALT
        {
            if (keystatus[0x4a])  // Keypad -
                g_visibility = min(g_visibility+(g_visibility>>3),16384);
            if (keystatus[0x4e])  // Keypad +
                g_visibility = max(g_visibility-(g_visibility>>3),128);
        }

        if (keystatus[keys[18]])   //Typing mode
        {
            keystatus[keys[18]] = 0;
            typemode = 1;
            keyFlushChars();
            // g_keyFIFOpos = g_keyFIFOend;      //Reset keyboard fifo
        }
    }
    else
    {
        while ((ch = keyGetChar()))
        {
            if (ch == 8)   //Backspace
            {
                if (typemessageleng == 0) { typemode = 0; break; }
                typemessageleng--;
            }
            else if (ch == 9)   // tab
            {
                keystatus[0xf] = 0;
                typemode = 0;
                break;
            }
            else if (ch == 13)  //Either ENTER
            {
                keystatus[0x1c] = 0; keystatus[0x9c] = 0;
                if (typemessageleng > 0)
                {
                    packbuf[0] = 2;          //Sending text is message type 4
                    for (j=typemessageleng-1; j>=0; j--)
                        packbuf[j+1] = typemessage[j];

                    for (i=connecthead; i>=0; i=connectpoint2[i])
                        if (i != myconnectindex)
                            sendpacket(i,packbuf,typemessageleng+1);

                    typemessageleng = 0;
                }
                typemode = 0;
                break;
            }
            else if ((typemessageleng < 159) && (ch >= 32) && (ch < 128))
            {
                typemessage[typemessageleng++] = ch;
            }
        }
    }
}

void initplayersprite(short snum)
{
    if (playersprite[snum] >= 0) return;

    spawnsprite(playersprite[snum],pos[snum].x,pos[snum].y,pos[snum].z+EYEHEIGHT,
                1+256,0,snum,32,64,64,0,0,PLAYER,ang[snum],0,0,0,snum+4096,
                cursectnum[snum],8,0,0,0);
}

void playback(void)
{
    int i, j, k;

    ready2send = 0;
    recstat = 0; i = reccnt;
    while (!keystatus[1])
    {
        if (handleevents())
        {
            if (quitevent)
            {
                keystatus[1] = 1;
                quitevent = 0;
            }
        }

        refreshaudio();

        while (totalclock >= lockclock+TICSPERFRAME)
        {
            timerUpdateClock();
            if (i >= reccnt)
            {
                prepareboard(boardfilename);
                for (i=connecthead; i>=0; i=connectpoint2[i])
                    initplayersprite((short)i);
                totalclock = 0;
                i = 0;
            }

            k = 0;
            for (j=connecthead; j>=0; j=connectpoint2[j])
            {
                copybufbyte(&recsync[i][k],&ffsync[j],sizeof(input));
                k++;
            }
            movethings(); domovethings();
            i++;
        }
        drawscreen(screenpeek,((int32_t) totalclock-gotlastpacketclock)*(65536/(TIMERINTSPERSECOND/MOVESPERSECOND)));

        if (keystatus[keys[15]])
        {
            keystatus[keys[15]] = 0;
            screenpeek = connectpoint2[screenpeek];
            if (screenpeek < 0) screenpeek = connecthead;
            drawstatusbar(screenpeek);   // Andy did this
        }
        if (keystatus[keys[14]])
        {
            keystatus[keys[14]] = 0;
            dimensionmode[screenpeek]++;
            if (dimensionmode[screenpeek] > 3) dimensionmode[screenpeek] = 1;
        }
    }

    musicoff();
    uninitmultiplayers();
    timerUninit();
    uninitinput();
    uninitsb();
    engineUnInit();
    uninitgroupfile();
    exit(0);
}

void setup3dscreen(void)
{
    int i, dax, day, dax2, day2;

    i = videoSetGameMode(fullscreen, xdimgame, ydimgame, bppgame, upscalefactor);
    if (i < 0)
    {
        printf("Error setting video mode.\n");
        sendlogoff();
        musicoff();
        uninitmultiplayers();
        timerUninit();
        uninitinput();
        uninitsb();
        engineUnInit();
        uninitgroupfile();
        exit(0);
    }

#if 0
    //Make that ugly pink into black in case it ever shows up!
    i = 0L;
    setpalette(255,1,(char *)&i);
    //outp(0x3c8,255); outp(0x3c9,0); outp(0x3c9,0); outp(0x3c9,0);
#endif

    screensize = xdim;
    if (screensize > xdim)
    {
        dax = 0; day = 0;
        dax2 = xdim-1; day2 = ydim-1;
    }
    else
    {
        dax = ((xdim-screensize)>>1);
        dax2 = dax+screensize-1;
        day = (((ydim-32)-scale(screensize,ydim-32,xdim))>>1);
        day2 = day + scale(screensize,ydim-32,xdim)-1;
        videoSetViewableArea(dax,day,dax2,day2);
    }

    renderFlushPerms();

    if (screensize < xdim)
        drawtilebackground(/*0L,0L,*/ BACKGROUND,8,0L,0L,xdim-1L,ydim-1L,0);     //Draw background

    if (screensize <= xdim)
        refreshstatusbar();
}

void findrandomspot(int *x, int *y, short *sectnum)
{
    short startwall, endwall, s, dasector;
    vec3_t da = { 0, 0, 0 };
    int minx, maxx, miny, maxy, cnt;

    for (cnt=256; cnt>=0; cnt--)
    {
        do
        {
            dasector = mulscale16(krand(),numsectors);
        }
        while ((sector[dasector].ceilingz+(8<<8) >= sector[dasector].floorz) || ((sector[dasector].lotag|sector[dasector].hitag) != 0) || ((sector[dasector].floorstat&1) != 0));

        startwall = sector[dasector].wallptr;
        endwall = startwall+sector[dasector].wallnum;
        if (endwall <= startwall) continue;

        da.x = da.y = 0;
        minx = 0x7fffffff; maxx = 0x80000000;
        miny = 0x7fffffff; maxy = 0x80000000;

        for (s=startwall; s<endwall; s++)
        {
            da.x += wall[s].x;
            da.y += wall[s].y;
            if (wall[s].x < minx) minx = wall[s].x;
            if (wall[s].x > maxx) maxx = wall[s].x;
            if (wall[s].y < miny) miny = wall[s].y;
            if (wall[s].y > maxy) maxy = wall[s].y;
        }

        if ((maxx-minx <= 256) || (maxy-miny <= 256)) continue;

        da.x /= (endwall-startwall);
        da.y /= (endwall-startwall);

        if (inside(da.x,da.y,dasector) == 0) continue;

        da.z = sector[dasector].floorz-(32<<8);
        if (pushmove(&da,&dasector,128L,4<<8,4<<8,CLIPMASK0) < 0) continue;

        *x = da.x; *y = da.y; *sectnum = dasector;
        return;
    }
}

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

    ox = *x; oy = *y;

    for (i=0; i<warpsectorcnt; i++)
        if (warpsectorlist[i] == *dasector)
        {
            j = sector[*dasector].hitag;
            do
            {
                i++;
                if (i >= warpsectorcnt) i = 0;
            }
            while (sector[warpsectorlist[i]].hitag != j);
            *dasector = warpsectorlist[i];
            break;
        }

    //Find center of sector
    startwall = sector[*dasector].wallptr;
    endwall = startwall+sector[*dasector].wallnum;
    dax = 0L, day = 0L;
    for (s=startwall; s<endwall; s++)
    {
        dax += wall[s].x, day += wall[s].y;
        if (wall[s].nextsector >= 0)
            i = s;
    }
    *x = dax / (endwall-startwall);
    *y = day / (endwall-startwall);
    *z = sector[*dasector].floorz-(32<<8);
    updatesector(*x,*y,dasector);
    dax = ((wall[i].x+wall[wall[i].point2].x)>>1);
    day = ((wall[i].y+wall[wall[i].point2].y)>>1);
    *daang = getangle(dax-*x,day-*y);

    wsayfollow("warp.wav",3072L+(krand()&127)-64,192L,&ox,&oy,0);
    wsayfollow("warp.wav",4096L+(krand()&127)-64,256L,x,y,0);
}

void warpsprite(short spritenum)
{
    short dasectnum;

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

    copybuf(&sprite[spritenum].x,&osprite[spritenum].x,3);
    changespritesect(spritenum,dasectnum);

    bitmap_clear(show2dsprite, spritenum);
    if (bitmap_test(show2dsector, dasectnum))
        bitmap_set(show2dsprite, spritenum);
}

void initlava(void)
{
    int x, y, z, r;

    for (z=0; z<32; z++) lavaradcnt[z] = 0;
    for (x=-16; x<=16; x++)
        for (y=-16; y<=16; y++)
        {
            r = ksqrt(x*x + y*y);
            lavaradx[r][lavaradcnt[r]] = x;
            lavarady[r][lavaradcnt[r]] = y;
            lavaradcnt[r]++;
        }

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

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

    lavanumdrops = 0;
    lavanumframes = 0;
}

#if defined(__WATCOMC__) && !defined(NOASM)
#pragma aux addlava = \
    "mov al, byte ptr [ebx-133]", \
    "mov dl, byte ptr [ebx-1]", \
    "add al, byte ptr [ebx-132]", \
    "add dl, byte ptr [ebx+131]", \
    "add al, byte ptr [ebx-131]", \
    "add dl, byte ptr [ebx+132]", \
    "add al, byte ptr [ebx+1]", \
    "add al, dl", \
    parm [ebx] \
    modify exact [eax edx]
int addlava(int);
#elif defined(_MSC_VER) && !defined(NOASM)
int addlava(void *b)
{
    _asm
    {
        mov ebx, b
        mov al, byte ptr [ebx-133]
        mov dl, byte ptr [ebx-1]
        add al, byte ptr [ebx-132]
        add dl, byte ptr [ebx+131]
        add al, byte ptr [ebx-131]
        add dl, byte ptr [ebx+132]
        add al, byte ptr [ebx+1]
        add al, dl
    }
}
#elif defined(__GNUC__) && defined(__i386__) && !defined(NOASM)
int addlava(void *b)
{
    int r;
    __asm__ __volatile__(
        "movb -133(%%ebx), %%al\n\t"
        "movb -1(%%ebx), %%dl\n\t"
        "addb -132(%%ebx), %%al\n\t"
        "addb 131(%%ebx), %%dl\n\t"
        "addb -131(%%ebx), %%al\n\t"
        "addb 132(%%ebx), %%dl\n\t"
        "addb 1(%%ebx), %%al\n\t"
        "addb %%dl, %%al"
        : "=a" (r) : "b" (b)
        : "dx"
        );
    return r;
}
#else
int addlava(void *bx)
{
    char *b = (char *)bx;
    return b[-133] + b[-132] + b[-131] + b[1] + b[-1] + b[131] + b[132];
}
#endif

void movelava(char *dapic)
{
    int i, /*j,*/ x, y, z, zz, dalavadropsiz, dadropsizlookup;
    int dalavax, dalavay, *ptr, *ptr2;
    char *pi, *pj, *py;

    for (z=min(LAVAMAXDROPS-lavanumdrops-1,3); z>=0; z--)
    {
        lavadropx[lavanumdrops] = (wrand()&(LAVASIZ-1));
        lavadropy[lavanumdrops] = (wrand()&(LAVASIZ-1));
        lavadropsiz[lavanumdrops] = 1;
        lavanumdrops++;
    }

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

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

    //Back up dapic with 1 pixel extra on each boundary
    //(to prevent anding for wrap-around)
    ptr = (int *)dapic;
    ptr2 = (int *)((LAVASIZ+4)+1+((intptr_t)lavabakpic));
    for (x=0; x<LAVASIZ; x++)
    {
        for (y=(LAVASIZ>>2); y>0; y--) *ptr2++ = ((*ptr++)&0x1f1f1f1f);
        ptr2++;
    }
    for (y=0; y<LAVASIZ; y++)
    {
        lavabakpic[y+1] = (dapic[y+((LAVASIZ-1)<<LAVALOGSIZ)]&31);
        lavabakpic[y+1+(LAVASIZ+1)*(LAVASIZ+4)] = (dapic[y]&31);
    }
    for (x=0; x<LAVASIZ; x++)
    {
        lavabakpic[(x+1)*(LAVASIZ+4)] = (dapic[(x<<LAVALOGSIZ)+(LAVASIZ-1)]&31);
        lavabakpic[(x+1)*(LAVASIZ+4)+(LAVASIZ+1)] = (dapic[x<<LAVALOGSIZ]&31);
    }
    lavabakpic[0] = (dapic[LAVASIZ*LAVASIZ-1]&31);
    lavabakpic[LAVASIZ+1] = (dapic[LAVASIZ*(LAVASIZ-1)]&31);
    lavabakpic[(LAVASIZ+4)*(LAVASIZ+1)] = (dapic[LAVASIZ-1]&31);
    lavabakpic[(LAVASIZ+4)*(LAVASIZ+2)-1] = (dapic[0]&31);

    ptr = (int *)dapic;
    for (x=0; x<LAVASIZ; x++)
    {
        pi = &lavabakpic[(x+1)*(LAVASIZ+4)+1];
        pj = pi+LAVASIZ;
        for (py=pi; py<pj; py+=4)
        {
            *ptr++ = ((addlava(&py[0])&0xf8)>>3)+
                     ((addlava(&py[1])&0xf8)<<5)+
                     ((addlava(&py[2])&0xf8)<<13)+
                     ((addlava(&py[3])&0xf8)<<21)+
                     0xc2c2c2c2;
        }
    }

    lavanumframes++;
}

void doanimations(void)
{
    int i, j;

    for (i=animatecnt-1; i>=0; i--)
    {
        j = *animateptr[i];

        if (j < animategoal[i])
            j = min(j+animatevel[i]*TICSPERFRAME,animategoal[i]);
        else
            j = max(j-animatevel[i]*TICSPERFRAME,animategoal[i]);
        animatevel[i] += animateacc[i];

        *animateptr[i] = j;

        if (j == animategoal[i])
        {
            animatecnt--;
            if (i != animatecnt)
            {
                stopinterpolation(animateptr[i]);
                animateptr[i] = animateptr[animatecnt];
                animategoal[i] = animategoal[animatecnt];
                animatevel[i] = animatevel[animatecnt];
                animateacc[i] = animateacc[animatecnt];
            }
        }
    }
}

int getanimationgoal(int *animptr)
{
    int i;

    for (i=animatecnt-1; i>=0; i--)
        if (animptr == animateptr[i]) return i;
    return -1;
}

int setanimation(int *animptr, int thegoal, int thevel, int theacc)
{
    int i, j;

    if (animatecnt >= MAXANIMATES) return -1;

    j = animatecnt;
    for (i=animatecnt-1; i>=0; i--)
        if (animptr == animateptr[i])
        { j = i; break; }

    setinterpolation(animptr);

    animateptr[j] = animptr;
    animategoal[j] = thegoal;
    animatevel[j] = thevel;
    animateacc[j] = theacc;
    if (j == animatecnt) animatecnt++;
    return j;
}

void checkmasterslaveswitch(void)
{
    int i, j;

    if (option[4] == 0) return;

    j = 0;
    for (i=connecthead; i>=0; i=connectpoint2[i])
        if (ssync[i].bits&512) j++;
    if (j != 1) return;

    i = connecthead;
    for (j=connectpoint2[i]; j>=0; j=connectpoint2[j])
    {
        if (ssync[j].bits&512)
        {
            connectpoint2[i] = connectpoint2[j];
            connectpoint2[j] = connecthead;
            connecthead = (short)j;

            oloc.fvel = loc.fvel+1;
            oloc.svel = loc.svel+1;
            oloc.avel = loc.avel+1;
            oloc.bits = loc.bits+1;
            for (i=0; i<MAXPLAYERS; i++)
            {
                osync[i].fvel = ffsync[i].fvel+1;
                osync[i].svel = ffsync[i].svel+1;
                osync[i].avel = ffsync[i].avel+1;
                osync[i].bits = ffsync[i].bits+1;
            }

            syncvalhead = othersyncvalhead = syncvaltail = 0L;
            totalclock = ototalclock = gotlastpacketclock = lockclock;

            j = 1;
            for (i=connecthead; i>=0; i=connectpoint2[i])
            {
                if (myconnectindex == i) break;
                j++;
            }
            if (j == 1)
                Bstrcpy(getmessage,"Player 1 (Master)");
            else
                Bsprintf(getmessage,"Player %d (Slave)",j);
            getmessageleng = Bstrlen(getmessage);
            getmessagetimeoff = (int32_t) totalclock+120;

            return;
        }
        i = j;
    }
}


int testneighborsectors(short sect1, short sect2)
{
    short i, startwall, num1, num2;

    num1 = sector[sect1].wallnum;
    num2 = sector[sect2].wallnum;
    if (num1 < num2) //Traverse walls of sector with fewest walls (for speed)
    {
        startwall = sector[sect1].wallptr;
        for (i=num1-1; i>=0; i--)
            if (wall[i+startwall].nextsector == sect2)
                return 1;
    }
    else
    {
        startwall = sector[sect2].wallptr;
        for (i=num2-1; i>=0; i--)
            if (wall[i+startwall].nextsector == sect1)
                return 1;
    }
    return 0;
}

int loadgame(void)
{
    int dummy = 0;
    int i;
    buildvfs_kfd fil;
    int tmpanimateptr[MAXANIMATES];

    if ((fil = kopen4load("save0000.gam",0)) == buildvfs_kfd_invalid)
        return -1;

    kdfread(&numplayers,4,1,fil);
    kdfread(&myconnectindex,4,1,fil);
    kdfread(&connecthead,4,1,fil);
    kdfread(connectpoint2,4,MAXPLAYERS,fil);

    //Make sure palookups get set, sprites will get overwritten later
    for (i=connecthead; i>=0; i=connectpoint2[i]) initplayersprite((short)i);

    for (i = 0; i < MAXPLAYERS; ++i)
        kdfread(&pos[i].x,4,1,fil);
    for (i = 0; i < MAXPLAYERS; ++i)
        kdfread(&pos[i].y,4,1,fil);
    for (i = 0; i < MAXPLAYERS; ++i)
        kdfread(&pos[i].z,4,1,fil);

    kdfread(horiz,4,MAXPLAYERS,fil);
    kdfread(zoom,4,MAXPLAYERS,fil);
    kdfread(hvel,4,MAXPLAYERS,fil);
    kdfread(ang,2,MAXPLAYERS,fil);
    kdfread(cursectnum,2,MAXPLAYERS,fil);
    kdfread(ocursectnum,2,MAXPLAYERS,fil);
    kdfread(playersprite,2,MAXPLAYERS,fil);
    kdfread(deaths,2,MAXPLAYERS,fil);
    kdfread(lastchaingun,4,MAXPLAYERS,fil);
    kdfread(health,4,MAXPLAYERS,fil);
    kdfread(numgrabbers,2,MAXPLAYERS,fil);
    kdfread(nummissiles,2,MAXPLAYERS,fil);
    kdfread(numbombs,2,MAXPLAYERS,fil);
    kdfread(flytime,4,MAXPLAYERS,fil);
    kdfread(oflags,2,MAXPLAYERS,fil);
    kdfread(dimensionmode,1,MAXPLAYERS,fil);
    kdfread(revolvedoorstat,1,MAXPLAYERS,fil);
    kdfread(revolvedoorang,2,MAXPLAYERS,fil);
    kdfread(revolvedoorrotang,2,MAXPLAYERS,fil);
    kdfread(revolvedoorx,4,MAXPLAYERS,fil);
    kdfread(revolvedoory,4,MAXPLAYERS,fil);

    kdfread(&numsectors,2,1,fil);
    kdfread(sector,sizeof(sectortype),numsectors,fil);
    kdfread(&numwalls,2,1,fil);
    kdfread(wall,sizeof(walltype),numwalls,fil);
    //Store all sprites (even holes) to preserve indeces
    kdfread(sprite,sizeof(spritetype),MAXSPRITES,fil);
    kdfread(headspritesect,2,MAXSECTORS+1,fil);
    kdfread(prevspritesect,2,MAXSPRITES,fil);
    kdfread(nextspritesect,2,MAXSPRITES,fil);
    kdfread(headspritestat,2,MAXSTATUS+1,fil);
    kdfread(prevspritestat,2,MAXSPRITES,fil);
    kdfread(nextspritestat,2,MAXSPRITES,fil);

    kdfread(&fvel,4,1,fil);
    kdfread(&svel,4,1,fil);
    kdfread(&avel,4,1,fil);

    kdfread(&locselectedgun,4,1,fil);
    kdfread(&loc.fvel,1,1,fil);
    kdfread(&oloc.fvel,1,1,fil);
    kdfread(&loc.svel,1,1,fil);
    kdfread(&oloc.svel,1,1,fil);
    kdfread(&loc.avel,1,1,fil);
    kdfread(&oloc.avel,1,1,fil);
    kdfread(&loc.bits,2,1,fil);
    kdfread(&oloc.bits,2,1,fil);

    kdfread(&locselectedgun2,4,1,fil);
    kdfread(&loc2.fvel,sizeof(input),1,fil);

    kdfread(ssync,sizeof(input),MAXPLAYERS,fil);
    kdfread(osync,sizeof(input),MAXPLAYERS,fil);

    kdfread(boardfilename,1,80,fil);
    kdfread(&screenpeek,2,1,fil);
    kdfread(&oldmousebstatus,2,1,fil);
    kdfread(&brightness,2,1,fil);
    kdfread(&neartagsector,2,1,fil);
    kdfread(&neartagwall,2,1,fil);
    kdfread(&neartagsprite,2,1,fil);
    kdfread(&lockclock,4,1,fil);
    kdfread(&neartagdist,4,1,fil);
    kdfread(&neartaghitdist,4,1,fil);

    kdfread(turnspritelist,2,16,fil);
    kdfread(&turnspritecnt,2,1,fil);
    kdfread(warpsectorlist,2,16,fil);
    kdfread(&warpsectorcnt,2,1,fil);
    kdfread(xpanningsectorlist,2,16,fil);
    kdfread(&xpanningsectorcnt,2,1,fil);
    kdfread(ypanningwalllist,2,64,fil);
    kdfread(&ypanningwallcnt,2,1,fil);
    kdfread(floorpanninglist,2,64,fil);
    kdfread(&floorpanningcnt,2,1,fil);
    kdfread(dragsectorlist,2,16,fil);
    kdfread(dragxdir,2,16,fil);
    kdfread(dragydir,2,16,fil);
    kdfread(&dragsectorcnt,2,1,fil);
    kdfread(dragx1,4,16,fil);
    kdfread(dragy1,4,16,fil);
    kdfread(dragx2,4,16,fil);
    kdfread(dragy2,4,16,fil);
    kdfread(dragfloorz,4,16,fil);
    kdfread(&swingcnt,2,1,fil);
    kdfread(swingwall,2,32*5,fil);
    kdfread(swingsector,2,32,fil);
    kdfread(swingangopen,2,32,fil);
    kdfread(swingangclosed,2,32,fil);
    kdfread(swingangopendir,2,32,fil);
    kdfread(swingang,2,32,fil);
    kdfread(swinganginc,2,32,fil);
    kdfread(swingx,4,32*8,fil);
    kdfread(swingy,4,32*8,fil);
    kdfread(revolvesector,2,4,fil);
    kdfread(revolveang,2,4,fil);
    kdfread(&revolvecnt,2,1,fil);
    kdfread(revolvex,4,4*16,fil);
    kdfread(revolvey,4,4*16,fil);
    kdfread(revolvepivotx,4,4,fil);
    kdfread(revolvepivoty,4,4,fil);
    kdfread(subwaytracksector,2,4*128,fil);
    kdfread(subwaynumsectors,2,4,fil);
    kdfread(&subwaytrackcnt,2,1,fil);
    kdfread(subwaystop,4,4*8,fil);
    kdfread(subwaystopcnt,4,4,fil);
    kdfread(subwaytrackx1,4,4,fil);
    kdfread(subwaytracky1,4,4,fil);
    kdfread(subwaytrackx2,4,4,fil);
    kdfread(subwaytracky2,4,4,fil);
    kdfread(subwayx,4,4,fil);
    kdfread(subwaygoalstop,4,4,fil);
    kdfread(subwayvel,4,4,fil);
    kdfread(subwaypausetime,4,4,fil);
    kdfread(waterfountainwall,2,MAXPLAYERS,fil);
    kdfread(waterfountaincnt,2,MAXPLAYERS,fil);
    kdfread(slimesoundcnt,2,MAXPLAYERS,fil);

    //Warning: only works if all pointers are in sector structures!
    kdfread(tmpanimateptr,4,MAXANIMATES,fil);
    for (i=MAXANIMATES-1; i>=0; i--)
        animateptr[i] = (int *)(tmpanimateptr[i]+(intptr_t)sector);

    kdfread(animategoal,4,MAXANIMATES,fil);
    kdfread(animatevel,4,MAXANIMATES,fil);
    kdfread(animateacc,4,MAXANIMATES,fil);
    kdfread(&animatecnt,4,1,fil);

    kdfread(&totalclock,4,1,fil);
    kdfread(&numframes,4,1,fil);
    kdfread(&randomseed,4,1,fil);
    kdfread(&numshades,2,1,fil);

    kdfread(&g_visibility,4,1,fil);
    kdfread(&parallaxvisibility,4,1,fil);
    kdfread(&parallaxtype,1,1,fil);
    kdfread(&dummy,4,1,fil);
    kdfread(&dummy,2,MAXPSKYTILES,fil);
    kdfread(&dummy,2,1,fil);

    kdfread(&mirrorcnt,2,1,fil);
    kdfread(mirrorwall,2,mirrorcnt,fil);
    kdfread(mirrorsector,2,mirrorcnt,fil);

    //I should save off interpolation list, but they're pointers :(
    numinterpolations = 0;
    startofdynamicinterpolations = 0;

    kclose(fil);

    for (i=connecthead; i>=0; i=connectpoint2[i]) initplayersprite((short)i);

    totalclock = lockclock;
    ototalclock = lockclock;

    Bstrcpy(getmessage,"Game loaded.");
    getmessageleng = Bstrlen(getmessage);
    getmessagetimeoff = (int32_t) totalclock+360+(getmessageleng<<4);
    return 0;
}

int savegame(void)
{
    int dummy = 0;
    int i;
    BFILE *fil;
    int tmpanimateptr[MAXANIMATES];

    if ((fil = Bfopen("save0000.gam","wb")) == 0) return -1;

    dfwrite(&numplayers,4,1,fil);
    dfwrite(&myconnectindex,4,1,fil);
    dfwrite(&connecthead,4,1,fil);
    dfwrite(connectpoint2,4,MAXPLAYERS,fil);

    for (i = 0; i < MAXPLAYERS; ++i)
        dfwrite(&pos[i].x,4,1,fil);
    for (i = 0; i < MAXPLAYERS; ++i)
        dfwrite(&pos[i].y,4,1,fil);
    for (i = 0; i < MAXPLAYERS; ++i)
        dfwrite(&pos[i].z,4,1,fil);

    dfwrite(horiz,4,MAXPLAYERS,fil);
    dfwrite(zoom,4,MAXPLAYERS,fil);
    dfwrite(hvel,4,MAXPLAYERS,fil);
    dfwrite(ang,2,MAXPLAYERS,fil);
    dfwrite(cursectnum,2,MAXPLAYERS,fil);
    dfwrite(ocursectnum,2,MAXPLAYERS,fil);
    dfwrite(playersprite,2,MAXPLAYERS,fil);
    dfwrite(deaths,2,MAXPLAYERS,fil);
    dfwrite(lastchaingun,4,MAXPLAYERS,fil);
    dfwrite(health,4,MAXPLAYERS,fil);
    dfwrite(numgrabbers,2,MAXPLAYERS,fil);
    dfwrite(nummissiles,2,MAXPLAYERS,fil);
    dfwrite(numbombs,2,MAXPLAYERS,fil);
    dfwrite(flytime,4,MAXPLAYERS,fil);
    dfwrite(oflags,2,MAXPLAYERS,fil);
    dfwrite(dimensionmode,1,MAXPLAYERS,fil);
    dfwrite(revolvedoorstat,1,MAXPLAYERS,fil);
    dfwrite(revolvedoorang,2,MAXPLAYERS,fil);
    dfwrite(revolvedoorrotang,2,MAXPLAYERS,fil);
    dfwrite(revolvedoorx,4,MAXPLAYERS,fil);
    dfwrite(revolvedoory,4,MAXPLAYERS,fil);

    dfwrite(&numsectors,2,1,fil);
    dfwrite(sector,sizeof(sectortype),numsectors,fil);
    dfwrite(&numwalls,2,1,fil);
    dfwrite(wall,sizeof(walltype),numwalls,fil);
    //Store all sprites (even holes) to preserve indeces
    dfwrite(sprite,sizeof(spritetype),MAXSPRITES,fil);
    dfwrite(headspritesect,2,MAXSECTORS+1,fil);
    dfwrite(prevspritesect,2,MAXSPRITES,fil);
    dfwrite(nextspritesect,2,MAXSPRITES,fil);
    dfwrite(headspritestat,2,MAXSTATUS+1,fil);
    dfwrite(prevspritestat,2,MAXSPRITES,fil);
    dfwrite(nextspritestat,2,MAXSPRITES,fil);

    dfwrite(&fvel,4,1,fil);
    dfwrite(&svel,4,1,fil);
    dfwrite(&avel,4,1,fil);

    dfwrite(&locselectedgun,4,1,fil);
    dfwrite(&loc.fvel,1,1,fil);
    dfwrite(&oloc.fvel,1,1,fil);
    dfwrite(&loc.svel,1,1,fil);
    dfwrite(&oloc.svel,1,1,fil);
    dfwrite(&loc.avel,1,1,fil);
    dfwrite(&oloc.avel,1,1,fil);
    dfwrite(&loc.bits,2,1,fil);
    dfwrite(&oloc.bits,2,1,fil);

    dfwrite(&locselectedgun2,4,1,fil);
    dfwrite(&loc2.fvel,sizeof(input),1,fil);

    dfwrite(ssync,sizeof(input),MAXPLAYERS,fil);
    dfwrite(osync,sizeof(input),MAXPLAYERS,fil);

    dfwrite(boardfilename,1,80,fil);
    dfwrite(&screenpeek,2,1,fil);
    dfwrite(&oldmousebstatus,2,1,fil);
    dfwrite(&brightness,2,1,fil);
    dfwrite(&neartagsector,2,1,fil);
    dfwrite(&neartagwall,2,1,fil);
    dfwrite(&neartagsprite,2,1,fil);
    dfwrite(&lockclock,4,1,fil);
    dfwrite(&neartagdist,4,1,fil);
    dfwrite(&neartaghitdist,4,1,fil);

    dfwrite(turnspritelist,2,16,fil);
    dfwrite(&turnspritecnt,2,1,fil);
    dfwrite(warpsectorlist,2,16,fil);
    dfwrite(&warpsectorcnt,2,1,fil);
    dfwrite(xpanningsectorlist,2,16,fil);
    dfwrite(&xpanningsectorcnt,2,1,fil);
    dfwrite(ypanningwalllist,2,64,fil);
    dfwrite(&ypanningwallcnt,2,1,fil);
    dfwrite(floorpanninglist,2,64,fil);
    dfwrite(&floorpanningcnt,2,1,fil);
    dfwrite(dragsectorlist,2,16,fil);
    dfwrite(dragxdir,2,16,fil);
    dfwrite(dragydir,2,16,fil);
    dfwrite(&dragsectorcnt,2,1,fil);
    dfwrite(dragx1,4,16,fil);
    dfwrite(dragy1,4,16,fil);
    dfwrite(dragx2,4,16,fil);
    dfwrite(dragy2,4,16,fil);
    dfwrite(dragfloorz,4,16,fil);
    dfwrite(&swingcnt,2,1,fil);
    dfwrite(swingwall,2,32*5,fil);
    dfwrite(swingsector,2,32,fil);
    dfwrite(swingangopen,2,32,fil);
    dfwrite(swingangclosed,2,32,fil);
    dfwrite(swingangopendir,2,32,fil);
    dfwrite(swingang,2,32,fil);
    dfwrite(swinganginc,2,32,fil);
    dfwrite(swingx,4,32*8,fil);
    dfwrite(swingy,4,32*8,fil);
    dfwrite(revolvesector,2,4,fil);
    dfwrite(revolveang,2,4,fil);
    dfwrite(&revolvecnt,2,1,fil);
    dfwrite(revolvex,4,4*16,fil);
    dfwrite(revolvey,4,4*16,fil);
    dfwrite(revolvepivotx,4,4,fil);
    dfwrite(revolvepivoty,4,4,fil);
    dfwrite(subwaytracksector,2,4*128,fil);
    dfwrite(subwaynumsectors,2,4,fil);
    dfwrite(&subwaytrackcnt,2,1,fil);
    dfwrite(subwaystop,4,4*8,fil);
    dfwrite(subwaystopcnt,4,4,fil);
    dfwrite(subwaytrackx1,4,4,fil);
    dfwrite(subwaytracky1,4,4,fil);
    dfwrite(subwaytrackx2,4,4,fil);
    dfwrite(subwaytracky2,4,4,fil);
    dfwrite(subwayx,4,4,fil);
    dfwrite(subwaygoalstop,4,4,fil);
    dfwrite(subwayvel,4,4,fil);
    dfwrite(subwaypausetime,4,4,fil);
    dfwrite(waterfountainwall,2,MAXPLAYERS,fil);
    dfwrite(waterfountaincnt,2,MAXPLAYERS,fil);
    dfwrite(slimesoundcnt,2,MAXPLAYERS,fil);

    //Warning: only works if all pointers are in sector structures!
    for (i=MAXANIMATES-1; i>=0; i--)
        tmpanimateptr[i] = (int)((intptr_t)animateptr[i]-(intptr_t)sector);
    dfwrite(tmpanimateptr,4,MAXANIMATES,fil);

    dfwrite(animategoal,4,MAXANIMATES,fil);
    dfwrite(animatevel,4,MAXANIMATES,fil);
    dfwrite(animateacc,4,MAXANIMATES,fil);
    dfwrite(&animatecnt,4,1,fil);

    dfwrite(&totalclock,4,1,fil);
    dfwrite(&numframes,4,1,fil);
    dfwrite(&randomseed,4,1,fil);
    dfwrite(&numshades,2,1,fil);

    dfwrite(&g_visibility,4,1,fil);
    dfwrite(&parallaxvisibility,4,1,fil);
    dfwrite(&parallaxtype,1,1,fil);
    dfwrite(&dummy,4,1,fil);
    dfwrite(&dummy,2,MAXPSKYTILES,fil);
    dfwrite(&dummy,2,1,fil);

    dfwrite(&mirrorcnt,2,1,fil);
    dfwrite(mirrorwall,2,mirrorcnt,fil);
    dfwrite(mirrorsector,2,mirrorcnt,fil);

    Bfclose(fil);

    Bstrcpy(getmessage,"Game saved.");
    getmessageleng = Bstrlen(getmessage);
    getmessagetimeoff = (int32_t) totalclock+360+(getmessageleng<<4);
    return 0;
}

void faketimerhandler(void)
{
    short other /*, packbufleng*/;
    int i, j, k, l;

    timerUpdateClock();
    if ((totalclock < ototalclock+(TIMERINTSPERSECOND/MOVESPERSECOND)) || (ready2send == 0)) return;
    ototalclock += (TIMERINTSPERSECOND/MOVESPERSECOND);

    getpackets();
    if (getoutputcirclesize() >= 16) return;
    getinput();

#if 0
    for (i=connecthead; i>=0; i=connectpoint2[i])
        if (i != myconnectindex)
        {
            k = (movefifoend[myconnectindex]-1)-movefifoend[i];
            myminlag[i] = min(myminlag[i],k);
            mymaxlag = max(mymaxlag,k);
        }

    if (((movefifoend[myconnectindex]-1)&(TIMERUPDATESIZ-1)) == 0)
    {
        i = mymaxlag-bufferjitter; mymaxlag = 0;
        if (i > 0) bufferjitter += ((2+i)>>2);
        else if (i < 0) bufferjitter -= ((2-i)>>2);
    }
#endif

    if (networkmode == 1)
    {
        packbuf[2] = 0; j = 3;
        if (loc.fvel != oloc.fvel) packbuf[j++] = loc.fvel, packbuf[2] |= 1;
        if (loc.svel != oloc.svel) packbuf[j++] = loc.svel, packbuf[2] |= 2;
        if (loc.avel != oloc.avel) packbuf[j++] = loc.avel, packbuf[2] |= 4;
        if ((loc.bits^oloc.bits)&0x00ff) packbuf[j++] = (loc.bits&255), packbuf[2] |= 8;
        if ((loc.bits^oloc.bits)&0xff00) packbuf[j++] = ((loc.bits>>8)&255), packbuf[2] |= 16;
        copybufbyte(&loc,&oloc,sizeof(input));

        copybufbyte(&loc,&baksync[movefifoend[myconnectindex]][myconnectindex],sizeof(input));
        movefifoend[myconnectindex] = ((movefifoend[myconnectindex]+1)&(MOVEFIFOSIZ-1));

        for (i=connecthead; i>=0; i=connectpoint2[i])
            if (i != myconnectindex)
            {
                packbuf[0] = 17;
                packbuf[1] = (char)((movefifoend[myconnectindex]-movefifoend[i])&(MOVEFIFOSIZ-1));

                k = j;
                if ((myconnectindex == connecthead) || ((i == connecthead) && (myconnectindex == connectpoint2[connecthead])))
                {
                    while (syncvalhead != syncvaltail)
                    {
                        packbuf[j++] = syncval[syncvaltail];
                        syncvaltail = ((syncvaltail+1)&(MOVEFIFOSIZ-1));
                    }
                }
                sendpacket(i,packbuf,j);
                j = k;
            }

        gotlastpacketclock = (int32_t) totalclock;
        return;
    }

    //MASTER (or 1 player game)
    if ((myconnectindex == connecthead) || (option[4] == 0))
    {
        copybufbyte(&loc,&ffsync[myconnectindex],sizeof(input));

        if (option[4] != 0)
        {
            packbuf[0] = 0;
            j = ((numplayers+1)>>1)+1;
            for (k=1; k<j; k++) packbuf[k] = 0;
            k = (1<<3);
            for (i=connecthead; i>=0; i=connectpoint2[i])
            {
                l = 0;
                if (ffsync[i].fvel != osync[i].fvel) packbuf[j++] = ffsync[i].fvel, l |= 1;
                if (ffsync[i].svel != osync[i].svel) packbuf[j++] = ffsync[i].svel, l |= 2;
                if (ffsync[i].avel != osync[i].avel) packbuf[j++] = ffsync[i].avel, l |= 4;
                if (ffsync[i].bits != osync[i].bits)
                {
                    packbuf[j++] = (ffsync[i].bits&255);
                    packbuf[j++] = ((ffsync[i].bits>>8)&255);
                    l |= 8;
                }
                packbuf[k>>3] |= (l<<(k&7));
                k += 4;

                copybufbyte(&ffsync[i],&osync[i],sizeof(input));
            }

            while (syncvalhead != syncvaltail)
            {
                packbuf[j++] = syncval[syncvaltail];
                syncvaltail = ((syncvaltail+1)&(MOVEFIFOSIZ-1));
            }

            for (i=connectpoint2[connecthead]; i>=0; i=connectpoint2[i])
                sendpacket(i,packbuf,j);
        }
        else if (numplayers >= 2)
        {
            if (keystatus[0xb5])
            {
                keystatus[0xb5] = 0;
                locselectedgun2++; if (locselectedgun2 >= 3) locselectedgun2 = 0;
            }

            //Second player on 1 computer mode
            loc2.fvel = min(max(fvel2,-128+8),127-8);
            loc2.svel = min(max(svel2,-128+8),127-8);
            loc2.avel = min(max(avel2,-128+16),127-16);
            loc2.bits = (locselectedgun2<<13);
            loc2.bits |= keystatus[0x45];                  //Stand high
            loc2.bits |= (keystatus[0x47]<<1);             //Stand low
            loc2.bits |= (1<<8);                           //Run
            loc2.bits |= (keystatus[0x49]<<2);             //Look up
            loc2.bits |= (keystatus[0x37]<<3);             //Look down
            loc2.bits |= (keystatus[0x50]<<10);            //Space
            loc2.bits |= (keystatus[0x52]<<11);            //Shoot

            other = connectpoint2[myconnectindex];
            if (other < 0) other = connecthead;

            copybufbyte(&loc2,&ffsync[other],sizeof(input));
        }
        movethings();  //Move EVERYTHING (you too!)
    }
    else                        //I am a SLAVE
    {
        packbuf[0] = 1; packbuf[1] = 0; j = 2;
        if (loc.fvel != oloc.fvel) packbuf[j++] = loc.fvel, packbuf[1] |= 1;
        if (loc.svel != oloc.svel) packbuf[j++] = loc.svel, packbuf[1] |= 2;
        if (loc.avel != oloc.avel) packbuf[j++] = loc.avel, packbuf[1] |= 4;
        if ((loc.bits^oloc.bits)&0x00ff) packbuf[j++] = (loc.bits&255), packbuf[1] |= 8;
        if ((loc.bits^oloc.bits)&0xff00) packbuf[j++] = ((loc.bits>>8)&255), packbuf[1] |= 16;
        copybufbyte(&loc,&oloc,sizeof(input));
        sendpacket(connecthead,packbuf,j);
    }
}

void getpackets(void)
{
    int i, j, k, l;
    int other = 0, packbufleng, movecnt;

    if (option[4] == 0) return;

    movecnt = 0;
    while ((packbufleng = getpacket(&other,packbuf)) > 0)
    {
        switch (packbuf[0])
        {
        case 0:  //[0] (receive master sync buffer)
            j = ((numplayers+1)>>1)+1; k = (1<<3);
            for (i=connecthead; i>=0; i=connectpoint2[i])
            {
                l = (packbuf[k>>3]>>(k&7));
                if (l&1) ffsync[i].fvel = packbuf[j++];
                if (l&2) ffsync[i].svel = packbuf[j++];
                if (l&4) ffsync[i].avel = packbuf[j++];
                if (l&8)
                {
                    ffsync[i].bits = ((short)packbuf[j])+(((short)packbuf[j+1])<<8);
                    j += 2;
                }
                k += 4;
            }

            while (j != packbufleng)
            {
                othersyncval[othersyncvalhead] = packbuf[j++];
                othersyncvalhead = ((othersyncvalhead+1)&(MOVEFIFOSIZ-1));
            }
            if ((syncvalhead != syncvaltottail) && (othersyncvalhead != syncvaltottail))
            {
                syncstat = 0;
                do
                {
                    syncstat |= (syncval[syncvaltottail]^othersyncval[syncvaltottail]);
                    syncvaltottail = ((syncvaltottail+1)&(MOVEFIFOSIZ-1));
                }
                while ((syncvalhead != syncvaltottail) && (othersyncvalhead != syncvaltottail));
            }

            movethings();        //Move all players and sprites
            movecnt++;
            break;
        case 1:  //[1] (receive slave sync buffer)
            j = 2; k = packbuf[1];
            if (k&1) ffsync[other].fvel = packbuf[j++];
            if (k&2) ffsync[other].svel = packbuf[j++];
            if (k&4) ffsync[other].avel = packbuf[j++];
            if (k&8) ffsync[other].bits = ((ffsync[other].bits&0xff00)|((short)packbuf[j++]));
            if (k&16) ffsync[other].bits = ((ffsync[other].bits&0x00ff)|(((short)packbuf[j++])<<8));
            break;
        case 2:
            getmessageleng = packbufleng-1;
            for (j=getmessageleng-1; j>=0; j--) getmessage[j] = packbuf[j+1];
            getmessagetimeoff = (int32_t) totalclock+360+(getmessageleng<<4);
            wsay("getstuff.wav",8192L,63L,63L); //Added 12/2004
            break;
        case 3:
            wsay("getstuff.wav",4096L,63L,63L);
            break;
#if 0
        case 5:
            playerreadyflag[other] = packbuf[1];
            if ((other == connecthead) && (packbuf[1] == 2))
                sendpacket(connecthead,packbuf,2);
            break;
#endif
        case 250:
            playerreadyflag[other]++;
            break;
        case 17:
            j = 3; k = packbuf[2];
            if (k&1) ffsync[other].fvel = packbuf[j++];
            if (k&2) ffsync[other].svel = packbuf[j++];
            if (k&4) ffsync[other].avel = packbuf[j++];
            if (k&8) ffsync[other].bits = ((ffsync[other].bits&0xff00)|((short)packbuf[j++]));
            if (k&16) ffsync[other].bits = ((ffsync[other].bits&0x00ff)|(((short)packbuf[j++])<<8));
            otherlag[other] = packbuf[1];

            copybufbyte(&ffsync[other],&baksync[movefifoend[other]][other],sizeof(input));
            movefifoend[other] = ((movefifoend[other]+1)&(MOVEFIFOSIZ-1));

            while (j != packbufleng)
            {
                othersyncval[othersyncvalhead] = packbuf[j++];
                othersyncvalhead = ((othersyncvalhead+1)&(MOVEFIFOSIZ-1));
            }
            if ((syncvalhead != syncvaltottail) && (othersyncvalhead != syncvaltottail))
            {
                syncstat = 0;
                do
                {
                    syncstat |= (syncval[syncvaltottail]^othersyncval[syncvaltottail]);
                    syncvaltottail = ((syncvaltottail+1)&(MOVEFIFOSIZ-1));
                }
                while ((syncvalhead != syncvaltottail) && (othersyncvalhead != syncvaltottail));
            }

            break;
        case 255:  //[255] (logout)
            keystatus[1] = 1;
            break;
        }
    }
    if ((networkmode == 0) && (myconnectindex != connecthead) && ((movecnt&1) == 0))
    {
        if (wrand()&1) ototalclock += (TICSPERFRAME>>1);
        else ototalclock -= (TICSPERFRAME>>1);
    }
}

void drawoverheadmap(int cposx, int cposy, int czoom, short cang)
{
    int i, j, k, l=0, x1, y1, x2=0, y2=0, x3, y3, x4, y4, ox, oy, xoff, yoff;
    int dax, day, cosang, sinang, xspan, yspan, sprx, spry;
    int xrepeat, yrepeat, z1, z2, startwall, endwall, tilenum, daang;
    int xvect, yvect, xvect2, yvect2;
    char col;
    walltype *wal, *wal2;
    spritetype *spr;

    int32_t tmpydim = (xdim*5)/8;

    renderSetAspect(65536, divscale16(tmpydim*320, xdim*200));

    xvect = sintable[(-cang)&2047] * czoom;
    yvect = sintable[(1536-cang)&2047] * czoom;
    xvect2 = mulscale16(xvect,yxaspect);
    yvect2 = mulscale16(yvect,yxaspect);

    //Draw red lines
    for (i=0; i<numsectors; i++)
    {
        startwall = sector[i].wallptr;
        endwall = sector[i].wallptr + sector[i].wallnum;

        z1 = sector[i].ceilingz; z2 = sector[i].floorz;

        for (j=startwall,wal=&wall[startwall]; j<endwall; j++,wal++)
        {
            k = wal->nextwall; if (k < 0) continue;

            if (!bitmap_test(show2dwall, j)) continue;
            if (k > j && bitmap_test(show2dwall, k)) continue;

            if (sector[wal->nextsector].ceilingz == z1)
                if (sector[wal->nextsector].floorz == z2)
                    if (((wal->cstat|wall[wal->nextwall].cstat)&(16+32)) == 0) continue;

            col = 152;

            if (dimensionmode[screenpeek] == 2)
            {
                if (sector[i].floorz != sector[i].ceilingz)
                    if (sector[wal->nextsector].floorz != sector[wal->nextsector].ceilingz)
                        if (((wal->cstat|wall[wal->nextwall].cstat)&(16+32)) == 0)
                            if (sector[i].floorz == sector[wal->nextsector].floorz) continue;
                if (sector[i].floorpicnum != sector[wal->nextsector].floorpicnum) continue;
                if (sector[i].floorshade != sector[wal->nextsector].floorshade) continue;
                col = 12;
            }

            ox = wal->x-cposx; oy = wal->y-cposy;
            x1 = dmulscale16(ox,xvect,-oy,yvect)+(xdim<<11);
            y1 = dmulscale16(oy,xvect2,ox,yvect2)+(ydim<<11);

            wal2 = &wall[wal->point2];
            ox = wal2->x-cposx; oy = wal2->y-cposy;
            x2 = dmulscale16(ox,xvect,-oy,yvect)+(xdim<<11);
            y2 = dmulscale16(oy,xvect2,ox,yvect2)+(ydim<<11);

            renderDrawLine(x1,y1,x2,y2,col);
        }
    }

    //Draw sprites
    k = playersprite[screenpeek];
    for (i=0; i<numsectors; i++)
        for (j=headspritesect[i]; j>=0; j=nextspritesect[j])
            if (bitmap_test(show2dsprite, j))
            {
                spr = &sprite[j]; if (spr->cstat&0x8000) continue;
                col = 56;
                if (spr->cstat&1) col = 248;
                if (j == k) col = 31;

                k = statrate[spr->statnum];
                sprx = spr->x;
                spry = spr->y;
                if (k >= 0)
                {
                    switch (k)
                    {
                    case 0: l = smoothratio; break;
                    case 1: l = (smoothratio>>1)+(((nummoves-j)&1)<<15); break;
                    case 3: l = (smoothratio>>2)+(((nummoves-j)&3)<<14); break;
                    case 7: l = (smoothratio>>3)+(((nummoves-j)&7)<<13); break;
                    case 15: l = (smoothratio>>4)+(((nummoves-j)&15)<<12); break;
                    }
                    sprx = osprite[j].x+mulscale16(sprx-osprite[j].x,l);
                    spry = osprite[j].y+mulscale16(spry-osprite[j].y,l);
                }

                switch (spr->cstat & CSTAT_SPRITE_ALIGNMENT)
                {
                case CSTAT_SPRITE_ALIGNMENT_FACING:
                    ox = sprx-cposx; oy = spry-cposy;
                    x1 = dmulscale16(ox,xvect,-oy,yvect);
                    y1 = dmulscale16(oy,xvect2,ox,yvect2);

                    if (dimensionmode[screenpeek] == 1)
                    {
                        ox = (sintable[(spr->ang+512)&2047]>>7);
                        oy = (sintable[(spr->ang)&2047]>>7);
                        x2 = dmulscale16(ox,xvect,-oy,yvect);
                        y2 = dmulscale16(oy,xvect,ox,yvect);

                        if (j == playersprite[screenpeek])
                        {
                            x2 = 0L;
                            y2 = -(czoom<<5);
                        }

                        x3 = mulscale16(x2,yxaspect);
                        y3 = mulscale16(y2,yxaspect);

                        renderDrawLine(x1-x2+(xdim<<11),y1-y3+(ydim<<11),
                                    x1+x2+(xdim<<11),y1+y3+(ydim<<11),col);
                        renderDrawLine(x1-y2+(xdim<<11),y1+x3+(ydim<<11),
                                    x1+x2+(xdim<<11),y1+y3+(ydim<<11),col);
                        renderDrawLine(x1+y2+(xdim<<11),y1-x3+(ydim<<11),
                                    x1+x2+(xdim<<11),y1+y3+(ydim<<11),col);
                    }
                    else
                    {
                        if (bitmap_test(gotsector, i) && czoom > 96)
                        {
                            daang = (spr->ang-cang)&2047;
                            if (j == playersprite[screenpeek]) { x1 = 0; y1 = 0; daang = 0; }
                            rotatesprite((x1<<4)+(xdim<<15),(y1<<4)+(ydim<<15),mulscale16(czoom*spr->yrepeat,yxaspect),daang,spr->picnum,spr->shade,spr->pal,(spr->cstat&2)>>1,windowxy1.x,windowxy1.y,windowxy2.x,windowxy2.y);
                        }
                    }
                    break;
                case CSTAT_SPRITE_ALIGNMENT_WALL:
                    x1 = sprx; y1 = spry;
                    tilenum = spr->picnum;
                    xoff = (int)picanm[tilenum].xofs+((int)spr->xoffset);
                    if ((spr->cstat&4) > 0) xoff = -xoff;
                    k = spr->ang; l = spr->xrepeat;
                    dax = sintable[k&2047]*l; day = sintable[(k+1536)&2047]*l;
                    l = tilesiz[tilenum].x; k = (l>>1)+xoff;
                    x1 -= mulscale16(dax,k); x2 = x1+mulscale16(dax,l);
                    y1 -= mulscale16(day,k); y2 = y1+mulscale16(day,l);

                    ox = x1-cposx; oy = y1-cposy;
                    x1 = dmulscale16(ox,xvect,-oy,yvect);
                    y1 = dmulscale16(oy,xvect2,ox,yvect2);

                    ox = x2-cposx; oy = y2-cposy;
                    x2 = dmulscale16(ox,xvect,-oy,yvect);
                    y2 = dmulscale16(oy,xvect2,ox,yvect2);

                    renderDrawLine(x1+(xdim<<11),y1+(ydim<<11),
                                x2+(xdim<<11),y2+(ydim<<11),col);

                    break;
                case CSTAT_SPRITE_ALIGNMENT_FLOOR:
                    if (dimensionmode[screenpeek] == 1)
                    {
                        tilenum = spr->picnum;
                        xoff = (int)picanm[tilenum].xofs+((int)spr->xoffset);
                        yoff = (int)picanm[tilenum].yofs+((int)spr->yoffset);
                        if ((spr->cstat&4) > 0) xoff = -xoff;
                        if ((spr->cstat&8) > 0) yoff = -yoff;

                        k = spr->ang;
                        cosang = sintable[(k+512)&2047]; sinang = sintable[k];
                        xspan = tilesiz[tilenum].x; xrepeat = spr->xrepeat;
                        yspan = tilesiz[tilenum].y; yrepeat = spr->yrepeat;

                        dax = ((xspan>>1)+xoff)*xrepeat; day = ((yspan>>1)+yoff)*yrepeat;
                        x1 = sprx + dmulscale16(sinang,dax,cosang,day);
                        y1 = spry + dmulscale16(sinang,day,-cosang,dax);
                        l = xspan*xrepeat;
                        x2 = x1 - mulscale16(sinang,l);
                        y2 = y1 + mulscale16(cosang,l);
                        l = yspan*yrepeat;
                        k = -mulscale16(cosang,l); x3 = x2+k; x4 = x1+k;
                        k = -mulscale16(sinang,l); y3 = y2+k; y4 = y1+k;

                        ox = x1-cposx; oy = y1-cposy;
                        x1 = dmulscale16(ox,xvect,-oy,yvect);
                        y1 = dmulscale16(oy,xvect2,ox,yvect2);

                        ox = x2-cposx; oy = y2-cposy;
                        x2 = dmulscale16(ox,xvect,-oy,yvect);
                        y2 = dmulscale16(oy,xvect2,ox,yvect2);

                        ox = x3-cposx; oy = y3-cposy;
                        x3 = dmulscale16(ox,xvect,-oy,yvect);
                        y3 = dmulscale16(oy,xvect2,ox,yvect2);

                        ox = x4-cposx; oy = y4-cposy;
                        x4 = dmulscale16(ox,xvect,-oy,yvect);
                        y4 = dmulscale16(oy,xvect2,ox,yvect2);

                        renderDrawLine(x1+(xdim<<11),y1+(ydim<<11),
                                    x2+(xdim<<11),y2+(ydim<<11),col);

                        renderDrawLine(x2+(xdim<<11),y2+(ydim<<11),
                                    x3+(xdim<<11),y3+(ydim<<11),col);

                        renderDrawLine(x3+(xdim<<11),y3+(ydim<<11),
                                    x4+(xdim<<11),y4+(ydim<<11),col);

                        renderDrawLine(x4+(xdim<<11),y4+(ydim<<11),
                                    x1+(xdim<<11),y1+(ydim<<11),col);

                    }
                    break;
                }
            }

    //Draw white lines
    for (i=0; i<numsectors; i++)
    {
        startwall = sector[i].wallptr;
        endwall = sector[i].wallptr + sector[i].wallnum;

        k = -1;
        for (j=startwall,wal=&wall[startwall]; j<endwall; j++,wal++)
        {
            if (wal->nextwall >= 0) continue;

            if (!bitmap_test(show2dwall, j)) continue;

            if (tilesiz[wal->picnum].x == 0) continue;
            if (tilesiz[wal->picnum].y == 0) continue;

            if (j == k)
            { x1 = x2; y1 = y2; }
            else
            {
                ox = wal->x-cposx; oy = wal->y-cposy;
                x1 = dmulscale16(ox,xvect,-oy,yvect)+(xdim<<11);
                y1 = dmulscale16(oy,xvect2,ox,yvect2)+(ydim<<11);
            }

            k = wal->point2; wal2 = &wall[k];
            ox = wal2->x-cposx; oy = wal2->y-cposy;
            x2 = dmulscale16(ox,xvect,-oy,yvect)+(xdim<<11);
            y2 = dmulscale16(oy,xvect2,ox,yvect2)+(ydim<<11);

            renderDrawLine(x1,y1,x2,y2,24);
        }
    }

    videoSetCorrectedAspect();
}

//New movesprite using getzrange.  Note that I made the getzrange
//parameters global (&globhiz,&globhihit,&globloz,&globlohit) so they
//don't need to be passed everywhere.  Also this should make this
//movesprite function compatible with the older movesprite functions.
int movesprite(short spritenum, int dx, int dy, int dz, int ceildist, int flordist, int clipmask)
{
    int daz, zoffs /*, tempint*/;
    short retval, dasectnum, datempshort;
    spritetype *spr;

    spr = &sprite[spritenum];

    if ((spr->cstat&128) == 0)
        zoffs = -((tilesiz[spr->picnum].y*spr->yrepeat)<<1);
    else
        zoffs = 0;

    dasectnum = spr->sectnum;  //Can't modify sprite sectors directly becuase of linked lists
    daz = spr->z+zoffs;  //Must do this if not using the new centered centering (of course)
    retval = clipmove_old(&spr->x,&spr->y,&daz,&dasectnum,dx,dy,
                          ((int)spr->clipdist)<<2,ceildist,flordist,clipmask);

    if (dasectnum < 0) retval = -1;

    if ((dasectnum != spr->sectnum) && (dasectnum >= 0))
        changespritesect(spritenum,dasectnum);

    //Set the blocking bit to 0 temporarly so getzrange doesn't pick up
    //its own sprite
    datempshort = spr->cstat; spr->cstat &= ~1;
    getzrange_old(spr->x,spr->y,spr->z-1,spr->sectnum,
                  &globhiz,&globhihit,&globloz,&globlohit,
                  ((int)spr->clipdist)<<2,clipmask);
    spr->cstat = datempshort;

    daz = spr->z+zoffs + dz;
    if ((daz <= globhiz) || (daz > globloz))
    {
        if (retval != 0) return retval;
        return 16384+dasectnum;
    }
    spr->z = daz-zoffs;
    return retval;
}


void waitforeverybody()
{
    int i;
    if (numplayers < 2) return;
    packbuf[0] = 250;
    for (i=connecthead; i>=0; i=connectpoint2[i])
    {
        if (i != myconnectindex) sendpacket(i,packbuf,1);
        if ((!networkmode) && (myconnectindex != connecthead)) break; //slaves in M/S mode only send to master
    }
    playerreadyflag[myconnectindex]++;
    while (1)
    {
        handleevents();
        refreshaudio();

        drawrooms(pos[myconnectindex].x,pos[myconnectindex].y,pos[myconnectindex].z,ang[myconnectindex],horiz[myconnectindex],cursectnum[myconnectindex]);
        if (!networkmode) Bsprintf((char *)tempbuf,"Master/slave mode");
        else Bsprintf((char *)tempbuf,"Peer-peer mode");
        printext256((xdim>>1)-(strlen((char *)tempbuf)<<2),(ydim>>1)-24,31,0,(char *)tempbuf,0);
        Bsprintf((char *)tempbuf,"Waiting for players");
        printext256((xdim>>1)-(strlen((char *)tempbuf)<<2),(ydim>>1)-16,31,0,(char *)tempbuf,0);
        for (i=connecthead; i>=0; i=connectpoint2[i])
        {
            if (playerreadyflag[i] < playerreadyflag[myconnectindex])
            {
                //slaves in M/S mode only wait for master
                if ((!networkmode) && (myconnectindex != connecthead) && (i != connecthead))
                {
                    Bsprintf((char *)tempbuf,"Player %d",i);
                    printext256((xdim>>1)-(16<<2),(ydim>>1)+i*8,15,0,(char *)tempbuf,0);
                }
                else
                {
                    Bsprintf((char *)tempbuf,"Player %d NOT ready",i);
                    printext256((xdim>>1)-(16<<2),(ydim>>1)+i*8,127,0,(char *)tempbuf,0);
                }
            }
            else
            {
                Bsprintf((char *)tempbuf,"Player %d ready",i);
                printext256((xdim>>1)-(16<<2),(ydim>>1)+i*8,31,0,(char *)tempbuf,0);
            }
            if (i == myconnectindex)
            {
                Bsprintf((char *)tempbuf,"You->");
                printext256((xdim>>1)-(26<<2),(ydim>>1)+i*8,95,0,(char *)tempbuf,0);
            }
        }
        videoNextPage();


        if (quitevent || keystatus[1])
        {
            sendlogoff();         //Signing off
            musicoff();
            uninitmultiplayers();
            timerUninit();
            uninitinput();
            uninitsb();
            engineUnInit();
            uninitgroupfile();
            exit(0);
        }

        getpackets();

        for (i=connecthead; i>=0; i=connectpoint2[i])
        {
            if (playerreadyflag[i] < playerreadyflag[myconnectindex]) break;
            if ((!networkmode) && (myconnectindex != connecthead)) { i = -1; break; } //slaves in M/S mode only wait for master
        }
        if (i < 0) return;
    }
}


void searchmap(short startsector)
{
    int i, j, dasect, splc, send, startwall, endwall;
    short dapic;
    walltype *wal;

    if (startsector < 0 || startsector >= numsectors)
        return;

    for (i = 0; i < bitmap_size(MAXWALLS); i++)
        show2dwall[i] = 0;
    for (i = 0; i < bitmap_size(MAXSPRITES); i++)
        show2dsprite[i] = 0;
    for (i = 0; i < bitmap_size(MAXSECTORS); i++)
        show2dsector[i] = 0;

    //Search your area recursively & set all show2dsector/show2dwalls
    tempshort[0] = startsector;
    bitmap_set(show2dsector, startsector);
    dapic = sector[startsector].ceilingpicnum;
    if (waloff[dapic] == 0) tileLoad(dapic);
    dapic = sector[startsector].floorpicnum;
    if (waloff[dapic] == 0) tileLoad(dapic);
    for (splc=0,send=1; splc<send; splc++)
    {
        dasect = tempshort[splc];
        startwall = sector[dasect].wallptr;
        endwall = startwall + sector[dasect].wallnum;
        for (i=startwall,wal=&wall[startwall]; i<endwall; i++,wal++)
        {
            bitmap_set(show2dwall, i);
            dapic = wall[i].picnum;
            if (waloff[dapic] == 0) tileLoad(dapic);
            dapic = wall[i].overpicnum;
            if (((dapic&0xfffff000) == 0) && (waloff[dapic] == 0)) tileLoad(dapic);

            j = wal->nextsector;
            if (j >= 0 && !bitmap_test(show2dsector, j))
            {
                bitmap_set(show2dsector, j);

                dapic = sector[j].ceilingpicnum;
                if (waloff[dapic] == 0) tileLoad(dapic);
                dapic = sector[j].floorpicnum;
                if (waloff[dapic] == 0) tileLoad(dapic);

                tempshort[send++] = (short)j;
            }
        }

        for (i=headspritesect[dasect]; i>=0; i=nextspritesect[i])
        {
            bitmap_set(show2dsprite, i);
            dapic = sprite[i].picnum;
            if (waloff[dapic] == 0) tileLoad(dapic);
        }
    }
}

void setinterpolation(int *posptr)
{
    int i;

    if (numinterpolations >= MAXINTERPOLATIONS) return;
    for (i=numinterpolations-1; i>=0; i--)
        if (curipos[i] == posptr) return;
    curipos[numinterpolations] = posptr;
    oldipos[numinterpolations] = *posptr;
    numinterpolations++;
}

void stopinterpolation(int *posptr)
{
    int i;

    for (i=numinterpolations-1; i>=startofdynamicinterpolations; i--)
        if (curipos[i] == posptr)
        {
            numinterpolations--;
            oldipos[i] = oldipos[numinterpolations];
            bakipos[i] = bakipos[numinterpolations];
            curipos[i] = curipos[numinterpolations];
        }
}

void updateinterpolations(void)  //Stick at beginning of domovethings
{
    int i;

    for (i=numinterpolations-1; i>=0; i--) oldipos[i] = *curipos[i];
}

void dointerpolations(void)       //Stick at beginning of drawscreen
{
    int i, j, odelta, ndelta;

    ndelta = 0; j = 0;
    for (i=numinterpolations-1; i>=0; i--)
    {
        bakipos[i] = *curipos[i];
        odelta = ndelta; ndelta = (*curipos[i])-oldipos[i];
        if (odelta != ndelta) j = mulscale16(ndelta,smoothratio);
        *curipos[i] = oldipos[i]+j;
    }
}

void restoreinterpolations(void)  //Stick at end of drawscreen
{
    int i;

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

void printext(int x, int y, char *buffer, short tilenum /*, char invisiblecol*/)
{
    int i;
    char ch;

    for (i=0; buffer[i]!=0; i++)
    {
        ch = (char)buffer[i];
        rotatesprite((x-((8&15)<<3))<<16,(y-((8>>4)<<3))<<16,65536L,0,tilenum,0,0,8+16+64+128,x,y,x+7,y+7);
        rotatesprite((x-((ch&15)<<3))<<16,(y-((ch>>4)<<3))<<16,65536L,0,tilenum,0,0,8+16+128,x,y,x+7,y+7);
        x += 8;
    }
}

void drawtilebackground(/*int thex, int they,*/ short tilenum,
                                                signed char shade, int cx1, int cy1,
                                                int cx2, int cy2, char dapalnum)
{
    int x, y, xsiz, ysiz, tx1, ty1, tx2, ty2;

    xsiz = tilesiz[tilenum].x; tx1 = cx1/xsiz; tx2 = cx2/xsiz;
    ysiz = tilesiz[tilenum].y; ty1 = cy1/ysiz; ty2 = cy2/ysiz;

    for (x=tx1; x<=tx2; x++)
        for (y=ty1; y<=ty2; y++)
            rotatesprite(x*xsiz<<16,y*ysiz<<16,65536L,0,tilenum,shade,dapalnum,8+16+64+128,cx1,cy1,cx2,cy2);
}

extern "C" void M32RunScript(const char *s) { UNREFERENCED_PARAMETER(s); }
void G_Polymer_UnInit(void) { }
void app_crashhandler(void) { }

/*
 * vim:ts=4:sw=4:
 */
