Subversion Repositories eduke32

Rev

Rev 5000 | Blame | Compare with Previous | Last modification | View Log | RSS feed

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

This file is part of EDuke32.

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

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

See the GNU General Public License for more details.

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

//-------------------------------------------------------------------------
#include "compat.h"

#include <time.h>
#include <stdlib.h>
#include <math.h>  // sqrt

#include "build.h"

#include "duke3d.h"
#include "gamedef.h"
#include "gameexec.h"
#include "scriplib.h"
#include "savegame.h"
#include "premap.h"
#include "osdcmds.h"
#include "osd.h"
#include "menus.h"
#include "input.h"
#include "anim.h"

#ifdef LUNATIC
# include "lunatic_game.h"
#endif

#if KRANDDEBUG
# define GAMEEXEC_INLINE
# define GAMEEXEC_STATIC
#else
# define GAMEEXEC_INLINE inline
# define GAMEEXEC_STATIC static
#endif

vmstate_t vm;

#if !defined LUNATIC
enum vmflags_t {
    VM_RETURN       = 0x00000001,
    VM_KILL         = 0x00000002,
    VM_NOEXECUTE    = 0x00000004,
};

int32_t g_tw;
int32_t g_errorLineNum;
int32_t g_currentEventExec = -1;

intptr_t const *insptr;

int32_t g_iReturnVarID = -1;     // var ID of "RETURN"
int32_t g_iWeaponVarID = -1;     // var ID of "WEAPON"
int32_t g_iWorksLikeVarID = -1;  // var ID of "WORKSLIKE"
int32_t g_iZRangeVarID = -1;     // var ID of "ZRANGE"
int32_t g_iAngRangeVarID = -1;   // var ID of "ANGRANGE"
int32_t g_iAimAngleVarID = -1;   // var ID of "AUTOAIMANGLE"
int32_t g_iLoTagID = -1;         // var ID of "LOTAG"
int32_t g_iHiTagID = -1;         // var ID of "HITAG"
int32_t g_iTextureID = -1;       // var ID of "TEXTURE"
int32_t g_iThisActorID = -1;     // var ID of "THISACTOR"
int32_t g_iSpriteVarID = -1;
int32_t g_iSectorVarID = -1;
int32_t g_iWallVarID = -1;
int32_t g_iPlayerVarID = -1;
int32_t g_iActorVarID = -1;

GAMEEXEC_STATIC void VM_Execute(int32_t loop);

# include "gamestructures.c"
#endif

#define VM_INSTMASK 0xfff

#define VM_CONDITIONAL(xxx)                                                                                            \
    {                                                                                                                  \
        if ((xxx) || ((insptr = (intptr_t *)*(insptr + 1)) && (((*insptr) & VM_INSTMASK) == CON_ELSE)))                \
        {                                                                                                              \
            insptr += 2;                                                                                               \
            VM_Execute(0);                                                                                             \
        }                                                                                                              \
    }


void VM_ScriptInfo(void)
{
#if !defined LUNATIC
    if (!script)
        return;

    if (insptr)
    {
        initprintf("\n");

        for (intptr_t const *p = insptr - 32; p < insptr + 32; p++)
        {
            if ((int32_t)(p - script) >= g_scriptSize)
                break;

            initprintf("%5d: %3d: ", (int32_t) (p - script), (int32_t) (p - insptr));

            if (*p >> 12 && (*p & VM_INSTMASK) < CON_END)
                initprintf("%5d %s\n", (int32_t) (*p >> 12), keyw[*p & VM_INSTMASK]);
            else
                initprintf("%d\n", (int32_t) *p);
        }

        initprintf("\n");
    }

    if (vm.g_i)
        initprintf("current actor: %d (%d)\n", vm.g_i, TrackerCast(vm.g_sp->picnum));

    initprintf("g_errorLineNum: %d, g_tw: %d\n", g_errorLineNum, g_tw);
#endif
}

static void VM_DeleteSprite(int32_t iActor, int32_t iPlayer)
{
    if (EDUKE32_PREDICT_FALSE((unsigned) iActor >= MAXSPRITES))
        return;

    // if player was set to squish, first stop that...
    if (EDUKE32_PREDICT_FALSE(iPlayer >= 0 && g_player[iPlayer].ps->actorsqu == iActor))
        g_player[iPlayer].ps->actorsqu = -1;

    A_DeleteSprite(iActor);
}

intptr_t *apScriptGameEvent[MAXGAMEEVENTS];

// May recurse, e.g. through EVENT_XXX -> ... -> EVENT_KILLIT
#ifdef LUNATIC
FORCE_INLINE int32_t VM_EventCommon_(int32_t iEventID, int32_t iActor, int32_t iPlayer, int32_t lDist, int32_t iReturn)
{
    const double t = gethiticks();
    int32_t ret = El_CallEvent(&g_ElState, iEventID, iActor, iPlayer, lDist, &iReturn);

    // NOTE: the run times are those of the called event plus any events
    // called by it, *not* "self" time.
    g_eventTotalMs[iEventID] += gethiticks()-t;
    g_eventCalls[iEventID]++;

    if (ret == 1)
        VM_DeleteSprite(iActor, iPlayer);

    return iReturn;
}
#else
FORCE_INLINE int32_t VM_EventCommon_(const int32_t iEventID, const int32_t iActor, const int32_t iPlayer,
                                     const int32_t lDist, int32_t iReturn)
{
    // this is initialized first thing because iActor, iPlayer, lDist, etc are already right there on the stack
    // from the function call
    const vmstate_t tempvm = { iActor, iPlayer, lDist, &actor[(unsigned)iActor].t_data[0],
                               &sprite[(unsigned)iActor], g_player[iPlayer].ps, 0 };

    // since we're targeting C99 and C++ now, we can interweave these to avoid
    // having to load addresses for things twice
    // for example, because we are loading backupReturnVar with the value of
    // aGameVars[g_iReturnVarID].val.lValue, the compiler can avoid having to
    // reload the address of aGameVars[g_iReturnVarID].val.lValue in order to
    // set it to the value of iReturn (...which should still be on the stack!)

    const int32_t backupReturnVar = aGameVars[g_iReturnVarID].val.lValue;
    aGameVars[g_iReturnVarID].val.lValue = iReturn;

    const int32_t backupEventExec = g_currentEventExec;
    g_currentEventExec = iEventID;

    intptr_t const *oinsptr = insptr;
    insptr = apScriptGameEvent[iEventID];

    const vmstate_t vm_backup = vm;
    vm = tempvm;

    // check tempvm instead of vm... this way, we are not actually loading
    // FROM vm anywhere until VM_Execute() is called
    if ((unsigned)tempvm.g_i >= MAXSPRITES)
    {
        static spritetype dummy_sprite;
        static int32_t dummy_t[ARRAY_SIZE(actor[0].t_data)];

        vm.g_sp = &dummy_sprite;
        vm.g_t = dummy_t;
    }

    if ((unsigned)iPlayer >= (unsigned)playerswhenstarted)
        vm.g_pp = g_player[0].ps;

    VM_Execute(1);

    if (vm.g_flags & VM_KILL)
        VM_DeleteSprite(vm.g_i, vm.g_p);

    // this needs to happen after VM_DeleteSprite() because VM_DeleteSprite()
    // can trigger additional events
    vm = vm_backup;
    insptr = oinsptr;
    g_currentEventExec = backupEventExec;
    iReturn = aGameVars[g_iReturnVarID].val.lValue;
    aGameVars[g_iReturnVarID].val.lValue = backupReturnVar;

    return iReturn;
}
#endif

// the idea here is that the compiler inlines the call to VM_EventCommon_() and gives us a set of full functions
// which are not only optimized further based on lDist or iReturn (or both) having values known at compile time,
// but are called faster due to having less parameters

int32_t VM_OnEventWithBoth_(int32_t iEventID, int32_t iActor, int32_t iPlayer, int32_t lDist, int32_t iReturn)
{
    return VM_EventCommon_(iEventID, iActor, iPlayer, lDist, iReturn);
}

int32_t VM_OnEventWithReturn_(int32_t iEventID, int32_t iActor, int32_t iPlayer, int32_t iReturn)
{
    return VM_EventCommon_(iEventID, iActor, iPlayer, -1, iReturn);
}

int32_t VM_OnEventWithDist_(int32_t iEventID, int32_t iActor, int32_t iPlayer, int32_t lDist)
{
    return VM_EventCommon_(iEventID, iActor, iPlayer, lDist, 0);
}

int32_t VM_OnEvent_(int32_t iEventID, int32_t iActor, int32_t iPlayer)
{
    return VM_EventCommon_(iEventID, iActor, iPlayer, -1, 0);
}

static int32_t VM_CheckSquished(void)
{
    sectortype const * const sc = &sector[vm.g_sp->sectnum];

    if (sc->lotag == ST_23_SWINGING_DOOR || EDUKE32_PREDICT_FALSE(vm.g_sp->picnum == APLAYER && ud.noclip))
        return 0;

    {
        int32_t fz=sc->floorz, cz=sc->ceilingz;
#ifdef YAX_ENABLE
        int16_t cb, fb;

        yax_getbunches(vm.g_sp->sectnum, &cb, &fb);
        if (cb >= 0 && (sc->ceilingstat&512)==0)  // if ceiling non-blocking...
            cz -= (32<<8);  // unconditionally don't squish... yax_getneighborsect is slowish :/
        if (fb >= 0 && (sc->floorstat&512)==0)
            fz += (32<<8);
#endif

        if (vm.g_sp->pal == 1 ?
            (fz - cz >= (32<<8) || (sc->lotag&32768)) :
            (fz - cz >= (12<<8)))
        return 0;
    }
   
    P_DoQuote(QUOTE_SQUISHED, vm.g_pp);

    if (A_CheckEnemySprite(vm.g_sp))
        vm.g_sp->xvel = 0;

    if (EDUKE32_PREDICT_FALSE(vm.g_sp->pal == 1)) // frozen
    {
        actor[vm.g_i].picnum = SHOTSPARK1;
        actor[vm.g_i].extra = 1;
        return 0;
    }

    return 1;
}

#if !defined LUNATIC
GAMEEXEC_STATIC GAMEEXEC_INLINE void P_ForceAngle(DukePlayer_t *p)
{
    int32_t n = 128-(krand()&255);

    p->horiz += 64;
    p->return_to_center = 9;
    p->look_ang = p->rotscrnang = n>>1;
}
#endif

int32_t A_Dodge(spritetype *s)
{
    int32_t bx,by,bxvect,byvect,i;
    int32_t mx = s->x, my = s->y;
    int32_t mxvect = sintable[(s->ang+512)&2047];
    int32_t myvect = sintable[s->ang&2047];

    if (A_CheckEnemySprite(s) && s->extra <= 0) // hack
        return 0;

    for (i=headspritestat[STAT_PROJECTILE]; i>=0; i=nextspritestat[i]) //weapons list
    {
        if (OW == i)
            continue;

        bx = SX-mx;
        by = SY-my;
        bxvect = sintable[(SA+512)&2047];
        byvect = sintable[SA&2047];

        if ((mxvect * bx) + (myvect * by) >= 0 && (bxvect * bx) + (byvect * by) < 0)
        {
            if (klabs((bxvect * by) - (byvect * bx)) < 65536<<6)
            {
                s->ang -= 512+(krand()&1024);
                return 1;
            }
        }
    }
    return 0;
}

int32_t A_GetFurthestAngle(int32_t iActor, int32_t angs)
{
    spritetype *s = &sprite[iActor];

    if (s->picnum != APLAYER && (AC_COUNT(actor[iActor].t_data)&63) > 2)
        return s->ang + 1024;

    {
        int32_t furthest_angle=0;
        int32_t d, j;
        int32_t greatestd = INT32_MIN;
        int32_t angincs=tabledivide32_noinline(2048, angs);
        hitdata_t hit;

        for (j=s->ang; j<(2048+s->ang); j+=angincs)
        {
            s->z -= (8<<8);
            hitscan((const vec3_t *)s, s->sectnum,
                    sintable[(j+512)&2047],
                    sintable[j&2047],0,
                    &hit,CLIPMASK1);
            s->z += (8<<8);
            d = klabs(hit.pos.x-s->x) + klabs(hit.pos.y-s->y);

            if (d > greatestd)
            {
                greatestd = d;
                furthest_angle = j;
            }
        }

        return furthest_angle&2047;
    }
}

int32_t A_FurthestVisiblePoint(int32_t iActor, tspritetype * const ts, int32_t *dax, int32_t *day)
{
    if (AC_COUNT(actor[iActor].t_data)&63)
        return -1;

    {
        int32_t d, da;//, d, cd, ca,tempx,tempy,cx,cy;
        int32_t j, angincs;
        spritetype *s = &sprite[iActor];
        hitdata_t hit;

        if ((!g_netServer && ud.multimode < 2) && ud.player_skill < 3)
            angincs = 2048/2;
        else angincs = tabledivide32_noinline(2048, 1+(krand()&1));

        for (j=ts->ang; j<(2048+ts->ang); j+=(angincs-(krand()&511)))
        {
            ts->z -= (16<<8);
            hitscan((const vec3_t *)ts, ts->sectnum,
                    sintable[(j+512)&2047],
                    sintable[j&2047],16384-(krand()&32767),
                    &hit,CLIPMASK1);

            ts->z += (16<<8);

            d = klabs(hit.pos.x-ts->x)+klabs(hit.pos.y-ts->y);
            da = klabs(hit.pos.x-s->x)+klabs(hit.pos.y-s->y);

            if (d < da && hit.sect > -1)
                if (cansee(hit.pos.x,hit.pos.y,hit.pos.z,
                           hit.sect,s->x,s->y,s->z-(16<<8),s->sectnum))
                {
                    *dax = hit.pos.x;
                    *day = hit.pos.y;
                    return hit.sect;
                }
        }
        return -1;
    }
}

void A_GetZLimits(int32_t iActor)
{
    spritetype *s = &sprite[iActor];

    int32_t hz,lz,zr = 127;
    int32_t cstat = s->cstat;

    s->cstat = 0;

    if (s->statnum == STAT_PROJECTILE)
        zr = 4;

    s->z -= ZOFFSET;
    getzrange((vec3_t *)s,s->sectnum,&actor[iActor].ceilingz,&hz,&actor[iActor].floorz,&lz,zr,CLIPMASK0);
    s->z += ZOFFSET;

    s->cstat = cstat;

    actor[iActor].flags &= ~SFLAG_NOFLOORSHADOW;

    if ((lz&49152) == 49152 && (sprite[lz&(MAXSPRITES-1)].cstat&48) == 0)
    {
        const spritetype *hitspr = &sprite[lz&(MAXSPRITES-1)];

        lz &= (MAXSPRITES-1);

        if ((A_CheckEnemySprite(hitspr) && hitspr->pal != 1 && s->statnum != STAT_PROJECTILE)
                || (hitspr->picnum == APLAYER && A_CheckEnemySprite(s)))
        {
            actor[iActor].flags |= SFLAG_NOFLOORSHADOW;  // No shadows on actors
            s->xvel = -256;
            A_SetSprite(iActor,CLIPMASK0);
        }
        else if (s->statnum == STAT_PROJECTILE && hitspr->picnum == APLAYER && s->owner==lz)
        {
            actor[iActor].ceilingz = sector[s->sectnum].ceilingz;
            actor[iActor].floorz   = sector[s->sectnum].floorz;
        }
    }
}

void A_Fall(int32_t iActor)
{
    spritetype *s = &sprite[iActor];
    int32_t hz,lz,c = g_spriteGravity;
#ifdef YAX_ENABLE
    int16_t fbunch;
#endif
    if (EDUKE32_PREDICT_FALSE(G_CheckForSpaceFloor(s->sectnum)))
        c = 0;
    else if (sector[s->sectnum].lotag == ST_2_UNDERWATER || EDUKE32_PREDICT_FALSE(G_CheckForSpaceCeiling(s->sectnum)))
        c = g_spriteGravity/6;

    if (s->statnum == STAT_ACTOR || s->statnum == STAT_PLAYER || s->statnum == STAT_ZOMBIEACTOR || s->statnum == STAT_STANDABLE)
    {
        int32_t cstat = s->cstat;
        s->cstat = 0;
        s->z -= ZOFFSET;
        getzrange((vec3_t *)s,s->sectnum,&actor[iActor].ceilingz,&hz,&actor[iActor].floorz,&lz,127L,CLIPMASK0);
        s->z += ZOFFSET;
        s->cstat = cstat;
    }
    else
    {
        actor[iActor].ceilingz = sector[s->sectnum].ceilingz;
        actor[iActor].floorz   = sector[s->sectnum].floorz;
    }

#ifdef YAX_ENABLE
    fbunch = (sector[s->sectnum].floorstat&512) ? -1 : yax_getbunch(s->sectnum, YAX_FLOOR);
#endif

    if (s->z < actor[iActor].floorz-ZOFFSET
#ifdef YAX_ENABLE
            || fbunch >= 0
#endif
       )
    {
        if (sector[s->sectnum].lotag == ST_2_UNDERWATER && s->zvel > 3122)
            s->zvel = 3144;
        s->z += s->zvel = min(6144, s->zvel+c);
    }

#ifdef YAX_ENABLE
    if (fbunch >= 0)
        setspritez(iActor, (vec3_t *)s);
    else
#endif
        if (s->z >= actor[iActor].floorz-ZOFFSET)
        {
            s->z = actor[iActor].floorz-ZOFFSET;
            s->zvel = 0;
        }
}

int32_t G_GetAngleDelta(int32_t a,int32_t na)
{
    a &= 2047;
    na &= 2047;

    if (klabs(a-na) < 1024)
    {
//        OSD_Printf("G_GetAngleDelta() returning %d\n",na-a);
        return (na-a);
    }

    if (na > 1024) na -= 2048;
    if (a > 1024) a -= 2048;

//    OSD_Printf("G_GetAngleDelta() returning %d\n",na-a);
    return (na-a);
}

GAMEEXEC_STATIC void VM_AlterAng(int32_t movflags)
{
    const int32_t ticselapsed = (AC_COUNT(vm.g_t))&31;

#if !defined LUNATIC
    const intptr_t *moveptr;
    if (EDUKE32_PREDICT_FALSE((unsigned)AC_MOVE_ID(vm.g_t) >= (unsigned)g_scriptSize-1))

    {
        AC_MOVE_ID(vm.g_t) = 0;
        OSD_Printf(OSD_ERROR "bad moveptr for actor %d (%d)!\n", vm.g_i, TrackerCast(vm.g_sp->picnum));
        return;
    }

    moveptr = script + AC_MOVE_ID(vm.g_t);

    vm.g_sp->xvel += (moveptr[0] - vm.g_sp->xvel)/5;
    if (vm.g_sp->zvel < 648)
        vm.g_sp->zvel += ((moveptr[1]<<4) - vm.g_sp->zvel)/5;
#else
    vm.g_sp->xvel += (actor[vm.g_i].mv.hvel - vm.g_sp->xvel)/5;
    if (vm.g_sp->zvel < 648)
        vm.g_sp->zvel += ((actor[vm.g_i].mv.vvel<<4) - vm.g_sp->zvel)/5;
#endif

    if (A_CheckEnemySprite(vm.g_sp) && vm.g_sp->extra <= 0) // hack
        return;

    if (movflags&seekplayer)
    {
        int32_t aang = vm.g_sp->ang, angdif, goalang;
        int32_t j = vm.g_pp->holoduke_on;

        // NOTE: looks like 'owner' is set to target sprite ID...

        if (j >= 0 && cansee(sprite[j].x,sprite[j].y,sprite[j].z,sprite[j].sectnum,vm.g_sp->x,vm.g_sp->y,vm.g_sp->z,vm.g_sp->sectnum))
            vm.g_sp->owner = j;
        else vm.g_sp->owner = vm.g_pp->i;

        if (sprite[vm.g_sp->owner].picnum == APLAYER)
            goalang = getangle(actor[vm.g_i].lastvx-vm.g_sp->x,actor[vm.g_i].lastvy-vm.g_sp->y);
        else
            goalang = getangle(sprite[vm.g_sp->owner].x-vm.g_sp->x,sprite[vm.g_sp->owner].y-vm.g_sp->y);

        if (vm.g_sp->xvel && vm.g_sp->picnum != DRONE)
        {
            angdif = G_GetAngleDelta(aang,goalang);

            if (ticselapsed < 2)
            {
                if (klabs(angdif) < 256)
                {
                    j = 128-(krand()&256);
                    vm.g_sp->ang += j;
                    if (A_GetHitscanRange(vm.g_i) < 844)
                        vm.g_sp->ang -= j;
                }
            }
            else if (ticselapsed > 18 && ticselapsed < GAMETICSPERSEC) // choose
            {
                if (klabs(angdif>>2) < 128) vm.g_sp->ang = goalang;
                else vm.g_sp->ang += angdif>>2;
            }
        }
        else vm.g_sp->ang = goalang;
    }

    if (ticselapsed < 1)
    {
        if (movflags&furthestdir)
        {
            vm.g_sp->ang = A_GetFurthestAngle(vm.g_i, 2);
            vm.g_sp->owner = vm.g_pp->i;
        }

        if (movflags&fleeenemy)
            vm.g_sp->ang = A_GetFurthestAngle(vm.g_i, 2);
    }
}

static inline void VM_AddAngle(int32_t shr, int32_t goalang)
{
    int32_t angdif = G_GetAngleDelta(vm.g_sp->ang,goalang)>>shr;

    if ((angdif > -8 && angdif < 0) || (angdif < 8 && angdif > 0))
        angdif *= 2;

    vm.g_sp->ang += angdif;
}

static void VM_FacePlayer(int32_t shr)
{
    int32_t goalang;

    if (vm.g_pp->newowner >= 0)
        goalang = getangle(vm.g_pp->opos.x-vm.g_sp->x, vm.g_pp->opos.y-vm.g_sp->y);
    else
        goalang = getangle(vm.g_pp->pos.x-vm.g_sp->x, vm.g_pp->pos.y-vm.g_sp->y);

    VM_AddAngle(shr, goalang);
}

////////// TROR get*zofslope //////////
// These rather belong into the engine.

static int32_t VM_GetCeilZOfSlope(void)
{
    const int dax = vm.g_sp->x, day = vm.g_sp->y;
    const int sectnum = vm.g_sp->sectnum;

#ifdef YAX_ENABLE
    if ((sector[sectnum].ceilingstat&512)==0)
    {
        int32_t nsect = yax_getneighborsect(dax, day, sectnum, YAX_CEILING);
        if (nsect >= 0)
            return getceilzofslope(nsect, dax, day);
    }
#endif
    return getceilzofslope(sectnum, dax, day);
}

static int32_t VM_GetFlorZOfSlope(void)
{
    const int dax = vm.g_sp->x, day = vm.g_sp->y;
    const int sectnum = vm.g_sp->sectnum;

#ifdef YAX_ENABLE
    if ((sector[sectnum].floorstat&512)==0)
    {
        int32_t nsect = yax_getneighborsect(dax, day, sectnum, YAX_FLOOR);
        if (nsect >= 0)
            return getflorzofslope(nsect, dax, day);
    }
#endif
    return getflorzofslope(sectnum, dax, day);
}

////////////////////

static int32_t A_GetWaterZOffset(int spritenum);

GAMEEXEC_STATIC void VM_Move(void)
{
#if !defined LUNATIC
    const intptr_t *moveptr;
#endif
    // NOTE: commented out condition is dead since r3159 (making hi/lotag unsigned).
    // XXX: Does it break anything? Where are movflags with all bits set created?
    const uint16_t *movflagsptr = &AC_MOVFLAGS(vm.g_sp, &actor[vm.g_i]);
    const int32_t movflags = /*(*movflagsptr==-1) ? 0 :*/ *movflagsptr;
    const int32_t deadflag = (A_CheckEnemySprite(vm.g_sp) && vm.g_sp->extra <= 0);

    AC_COUNT(vm.g_t)++;

    if (AC_MOVE_ID(vm.g_t) == 0 || movflags == 0)
    {
        if (deadflag || (actor[vm.g_i].bpos.x != vm.g_sp->x) || (actor[vm.g_i].bpos.y != vm.g_sp->y))
        {
            actor[vm.g_i].bpos.x = vm.g_sp->x;
            actor[vm.g_i].bpos.y = vm.g_sp->y;
            setsprite(vm.g_i, (vec3_t *)vm.g_sp);
        }
        return;
    }

    if (deadflag)
        goto dead;

    if (movflags&face_player)
        VM_FacePlayer(2);

    if (movflags&spin)
        vm.g_sp->ang += sintable[((AC_COUNT(vm.g_t)<<3)&2047)]>>6;

    if (movflags&face_player_slow)
        VM_FacePlayer(4);

    if ((movflags&jumptoplayer_bits) == jumptoplayer_bits)
    {
        if (AC_COUNT(vm.g_t) < 16)
            vm.g_sp->zvel -= (sintable[(512+(AC_COUNT(vm.g_t)<<4))&2047]>>5);
    }

    if (movflags&face_player_smart)
    {
        int32_t newx = vm.g_pp->pos.x + (vm.g_pp->vel.x/768);
        int32_t newy = vm.g_pp->pos.y + (vm.g_pp->vel.y/768);
        int32_t goalang = getangle(newx-vm.g_sp->x,newy-vm.g_sp->y);
        VM_AddAngle(2, goalang);
    }

dead:
#if !defined LUNATIC
    if (EDUKE32_PREDICT_FALSE((unsigned)AC_MOVE_ID(vm.g_t) >= (unsigned)g_scriptSize-1))
    {
        AC_MOVE_ID(vm.g_t) = 0;
        OSD_Printf(OSD_ERROR "clearing bad moveptr for actor %d (%d)\n", vm.g_i, TrackerCast(vm.g_sp->picnum));
        return;
    }

    moveptr = script + AC_MOVE_ID(vm.g_t);

    if (movflags&geth) vm.g_sp->xvel += ((moveptr[0])-vm.g_sp->xvel)>>1;
    if (movflags&getv) vm.g_sp->zvel += ((moveptr[1]<<4)-vm.g_sp->zvel)>>1;
#else
    if (movflags&geth) vm.g_sp->xvel += (actor[vm.g_i].mv.hvel - vm.g_sp->xvel)>>1;
    if (movflags&getv) vm.g_sp->zvel += (16*actor[vm.g_i].mv.vvel - vm.g_sp->zvel)>>1;
#endif

    if (movflags&dodgebullet && !deadflag)
        A_Dodge(vm.g_sp);

    if (vm.g_sp->picnum != APLAYER)
        VM_AlterAng(movflags);

    if (vm.g_sp->xvel > -6 && vm.g_sp->xvel < 6)
        vm.g_sp->xvel = 0;

    int badguyp = A_CheckEnemySprite(vm.g_sp);

    if (vm.g_sp->xvel || vm.g_sp->zvel)
    {
        int32_t daxvel = vm.g_sp->xvel;
        int32_t angdif = vm.g_sp->ang;

        if (badguyp && vm.g_sp->picnum != ROTATEGUN)
        {
            if ((vm.g_sp->picnum == DRONE || vm.g_sp->picnum == COMMANDER) && vm.g_sp->extra > 0)
            {
                if (vm.g_sp->picnum == COMMANDER)
                {
                    int32_t l;
                    // NOTE: COMMANDER updates both actor[].floorz and
                    // .ceilingz regardless of its zvel.
                    actor[vm.g_i].floorz = l = VM_GetFlorZOfSlope();
                    if (vm.g_sp->z > l-(8<<8))
                    {
                        vm.g_sp->z = l-(8<<8);
                        vm.g_sp->zvel = 0;
                    }

                    actor[vm.g_i].ceilingz = l = VM_GetCeilZOfSlope();
                    if (vm.g_sp->z < l+(80<<8))
                    {
                        vm.g_sp->z = l+(80<<8);
                        vm.g_sp->zvel = 0;
                    }
                }
                else
                {
                    int32_t l;
                    // The DRONE updates either .floorz or .ceilingz, not both.
                    if (vm.g_sp->zvel > 0)
                    {
                        actor[vm.g_i].floorz = l = VM_GetFlorZOfSlope();
                        if (vm.g_sp->z > l-(30<<8))
                            vm.g_sp->z = l-(30<<8);
                    }
                    else
                    {
                        actor[vm.g_i].ceilingz = l = VM_GetCeilZOfSlope();
                        if (vm.g_sp->z < l+(50<<8))
                        {
                            vm.g_sp->z = l+(50<<8);
                            vm.g_sp->zvel = 0;
                        }
                    }
                }
            }
            else if (vm.g_sp->picnum != ORGANTIC)
            {
                int32_t l;

                // In Duke3D, all other actors don't update .floorz or
                // .ceilingz here. In EDuke32, they do.

                if (vm.g_sp->zvel > 0)
                {
                    actor[vm.g_i].floorz = l = VM_GetFlorZOfSlope();
                    if (vm.g_sp->z > l)
                        vm.g_sp->z = l;
                    vm.g_sp->z += A_GetWaterZOffset(vm.g_i);
                }
                else if (vm.g_sp->zvel < 0)
                {
                    actor[vm.g_i].ceilingz = l = VM_GetCeilZOfSlope();
                    if (vm.g_sp->z < l+(66<<8))
                    {
                        vm.g_sp->z = l+(66<<8);
                        vm.g_sp->zvel >>= 1;
                    }
                }
            }

            if (vm.g_x < 960 && vm.g_sp->xrepeat > 16)
            {
                daxvel = -(1024 - vm.g_x);
                angdif = getangle(vm.g_pp->pos.x - vm.g_sp->x, vm.g_pp->pos.y - vm.g_sp->y);

                if (vm.g_x < 512)
                {
                    vm.g_pp->vel.x = 0;
                    vm.g_pp->vel.y = 0;
                }
                else
                {
                    vm.g_pp->vel.x = mulscale16(vm.g_pp->vel.x, vm.g_pp->runspeed - 0x2000);
                    vm.g_pp->vel.y = mulscale16(vm.g_pp->vel.y, vm.g_pp->runspeed - 0x2000);
                }
            }
            else if (vm.g_sp->picnum != DRONE && vm.g_sp->picnum != SHARK && vm.g_sp->picnum != COMMANDER)
            {
                if (vm.g_pp->actorsqu == vm.g_i)
                    return;

                if (!A_CheckSpriteFlags(vm.g_i, SFLAG_SMOOTHMOVE))
                {
                    if (AC_COUNT(vm.g_t) & 1)
                        return;
                    daxvel <<= 1;
                }
            }
        }
        else if (vm.g_sp->picnum == APLAYER)
            if (vm.g_sp->z < actor[vm.g_i].ceilingz+(32<<8))
                vm.g_sp->z = actor[vm.g_i].ceilingz+(32<<8);

        vec3_t tmpvect = { (daxvel * (sintable[(angdif + 512) & 2047])) >> 14,
                           (daxvel * (sintable[angdif & 2047])) >> 14, vm.g_sp->zvel };

        actor[vm.g_i].movflag =
            A_MoveSprite(vm.g_i, &tmpvect, (A_CheckSpriteFlags(vm.g_i, SFLAG_NOCLIP) ? 0 : CLIPMASK0));
    }

    if (!badguyp)
        return;

    vm.g_sp->shade += (sector[vm.g_sp->sectnum].ceilingstat & 1) ?
                      (sector[vm.g_sp->sectnum].ceilingshade - vm.g_sp->shade) >> 1 :
                      (sector[vm.g_sp->sectnum].floorshade - vm.g_sp->shade) >> 1;
}

static void P_AddWeaponMaybeSwitch(DukePlayer_t *ps, int32_t weap)
{
    if ((ps->weaponswitch & (1|4)) == (1|4))
    {
        const int32_t snum = P_Get(ps->i);
        int32_t i, new_wchoice = -1, curr_wchoice = -1;

        for (i=0; i<=FREEZE_WEAPON && (new_wchoice < 0 || curr_wchoice < 0); i++)
        {
            int32_t w = g_player[snum].wchoice[i];

            if (w == KNEE_WEAPON)
                w = FREEZE_WEAPON;
            else w--;

            if (w == ps->curr_weapon)
                curr_wchoice = i;
            if (w == weap)
                new_wchoice = i;
        }

        P_AddWeapon(ps, weap, (new_wchoice < curr_wchoice));
    }
    else
    {
        P_AddWeapon(ps, weap, (ps->weaponswitch & 1));
    }
}

#if defined LUNATIC
void P_AddWeaponMaybeSwitchI(int32_t snum, int32_t weap)
{
    P_AddWeaponMaybeSwitch(g_player[snum].ps, weap);
}
#else
static void P_AddWeaponAmmoCommon(DukePlayer_t *ps, int32_t weap, int32_t amount)
{
    P_AddAmmo(weap, ps, amount);

    if (PWEAPON(vm.g_p, ps->curr_weapon, WorksLike) == KNEE_WEAPON && (ps->gotweapon & (1 << weap)))
        P_AddWeaponMaybeSwitch(ps, weap);
}

static int32_t VM_AddWeapon(int32_t weap, int32_t amount, DukePlayer_t *ps)
{
    if (EDUKE32_PREDICT_FALSE((unsigned)weap >= MAX_WEAPONS))
    {
        CON_ERRPRINTF("Invalid weapon ID %d\n", weap);
        return 1;
    }

    if ((ps->gotweapon & (1 << weap)) == 0)
    {
        P_AddWeaponMaybeSwitch(ps, weap);
    }
    else if (ps->ammo_amount[weap] >= ps->max_ammo_amount[weap])
    {
        vm.g_flags |= VM_NOEXECUTE;
        return 2;
    }

    P_AddWeaponAmmoCommon(ps, weap, amount);

    return 0;
}
#endif

static int32_t A_GetVerticalVel(const actor_t *ac)
{
#ifdef LUNATIC
    return ac->mv.vvel;
#else
    int32_t moveScriptOfs = AC_MOVE_ID(ac->t_data);

    if ((unsigned)moveScriptOfs < (unsigned)g_scriptSize-1)
        return script[moveScriptOfs + 1];
    else
        return 0;
#endif
}

static int32_t A_GetWaterZOffset(int spritenum)
{
    const spritetype *const sp = &sprite[spritenum];
    const actor_t *const ac = &actor[spritenum];

    if (sector[sp->sectnum].lotag == ST_1_ABOVE_WATER)
    {
        if (A_CheckSpriteFlags(spritenum, SFLAG_NOWATERDIP))
            return 0;

        // fix for flying/jumping monsters getting stuck in water
        if ((AC_MOVFLAGS(sp, ac) & jumptoplayer_only) ||
            (G_HaveActor(sp->picnum) && A_GetVerticalVel(ac) != 0))
            return 0;

        return ACTOR_ONWATER_ADDZ;
    }

    return 0;
}

static void VM_Fall(int32_t g_i, spritetype *g_sp)
{
    int32_t grav = g_spriteGravity;

    g_sp->xoffset = g_sp->yoffset = 0;

    if (sector[g_sp->sectnum].lotag == ST_2_UNDERWATER || EDUKE32_PREDICT_FALSE(G_CheckForSpaceCeiling(g_sp->sectnum)))
        grav = g_spriteGravity/6;
    else if (EDUKE32_PREDICT_FALSE(G_CheckForSpaceFloor(g_sp->sectnum)))
        grav = 0;

    if (!actor[g_i].cgg-- || (sector[g_sp->sectnum].floorstat&2))
    {
        A_GetZLimits(g_i);
        actor[g_i].cgg = 3;
    }

    if (g_sp->z < actor[g_i].floorz-ZOFFSET)
    {
        // Free fall.
        g_sp->zvel = min(g_sp->zvel+grav, ACTOR_MAXFALLINGZVEL);
        int32_t z = g_sp->z + g_sp->zvel;

#ifdef YAX_ENABLE
        if (yax_getbunch(g_sp->sectnum, YAX_FLOOR) >= 0 &&
                (sector[g_sp->sectnum].floorstat&512)==0)
            setspritez(g_i, (vec3_t *)g_sp);
        else
#endif
            if (z > actor[g_i].floorz - ZOFFSET)
                z = actor[g_i].floorz - ZOFFSET;

        g_sp->z = z;
        return;
    }

    // Preliminary new z position of the actor.
    int32_t z = actor[g_i].floorz - ZOFFSET;

    if (A_CheckEnemySprite(g_sp) || (g_sp->picnum == APLAYER && g_sp->owner >= 0))
    {
        if (g_sp->zvel > 3084 && g_sp->extra <= 1)
        {
            // I'm guessing this DRONE check is from a beta version of the game
            // where they crashed into the ground when killed
            if (!(g_sp->picnum == APLAYER && g_sp->extra > 0) && g_sp->pal != 1 && g_sp->picnum != DRONE)
            {
                A_DoGuts(g_i,JIBS6,15);
                A_PlaySound(SQUISHED,g_i);
                A_Spawn(g_i,BLOODPOOL);
            }

            actor[g_i].picnum = SHOTSPARK1;
            actor[g_i].extra = 1;
            g_sp->zvel = 0;
        }
        else if (g_sp->zvel > 2048 && sector[g_sp->sectnum].lotag != ST_1_ABOVE_WATER)
        {
            int16_t newsect = g_sp->sectnum;

            pushmove((vec3_t *)g_sp, &newsect, 128, 4<<8, 4<<8, CLIPMASK0);
            if ((unsigned)newsect < MAXSECTORS)
                changespritesect(g_i, newsect);

            A_PlaySound(THUD, g_i);
        }
    }

    if (sector[g_sp->sectnum].lotag == ST_1_ABOVE_WATER)
    {
        g_sp->z = z + A_GetWaterZOffset(g_i);
        return;
    }

    g_sp->z = z;
    g_sp->zvel = 0;
}

static int32_t VM_ResetPlayer(int32_t g_p, int32_t g_flags)
{
    //AddLog("resetplayer");
    if (!g_netServer && ud.multimode < 2)
    {
        if (g_lastSaveSlot >= 0 && ud.recstat != 2)
        {
            M_OpenMenu(g_p);
            KB_ClearKeyDown(sc_Space);
            I_AdvanceTriggerClear();
            M_ChangeMenu(MENU_RESETPLAYER);
        }
        else g_player[g_p].ps->gm = MODE_RESTART;
#if !defined LUNATIC
        g_flags |= VM_NOEXECUTE;
#endif
    }
    else
    {
        if (g_p == myconnectindex)
        {
            CAMERADIST = 0;
            CAMERACLOCK = totalclock;
        }

        if (g_fakeMultiMode)
            P_ResetPlayer(g_p);
#ifndef NETCODE_DISABLE
        if (g_netServer)
        {
            P_ResetPlayer(g_p);
            Net_SpawnPlayer(g_p);
        }
#endif
    }

    P_UpdateScreenPal(g_player[g_p].ps);
    //AddLog("EOF: resetplayer");

    return g_flags;
}

void G_GetTimeDate(int32_t *vals)
{
    time_t rawtime;
    struct tm *ti;

    time(&rawtime);
    ti=localtime(&rawtime);
    // initprintf("Time&date: %s\n",asctime (ti));

    vals[0] = ti->tm_sec;
    vals[1] = ti->tm_min;
    vals[2] = ti->tm_hour;
    vals[3] = ti->tm_mday;
    vals[4] = ti->tm_mon;
    vals[5] = ti->tm_year+1900;
    vals[6] = ti->tm_wday;
    vals[7] = ti->tm_yday;
}

int32_t G_StartTrack(int32_t level)
{
    if ((unsigned)level < MAXLEVELS)
    {
        int32_t musicIndex = MAXLEVELS*ud.volume_number + level;

        if (MapInfo[musicIndex].musicfn != NULL)
        {
            // Only set g_musicIndex on success.
            g_musicIndex = musicIndex;
            S_PlayMusic(MapInfo[musicIndex].musicfn);

            return 0;
        }
    }

    return 1;
}

LUNATIC_EXTERN void G_ShowView(int32_t x, int32_t y, int32_t z, int32_t a, int32_t horiz, int32_t sect,
                               int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t unbiasedp)
{
    int32_t smoothratio = calc_smoothratio(totalclock, ototalclock);
#ifdef USE_OPENGL
    int32_t oprojhacks;
#endif

    if (g_screenCapture)
        return;

    if (offscreenrendering)
    {
        clearview(0);
        return;
    }

    if (x1 > x2) swaplong(&x1,&x2);
    if (y1 > y2) swaplong(&y1,&y2);

    if (!unbiasedp)
    {
        // The showview command has a rounding bias towards zero,
        // e.g. floor((319*1680)/320) == 1674
        x1 = scale(x1,xdim,320);
        y1 = scale(y1,ydim,200);
        x2 = scale(x2,xdim,320);
        y2 = scale(y2,ydim,200);
    }
    else
    {
        // This will map the maximum 320-based coordinate to the
        // maximum real screen coordinate:
        // floor((319*1679)/319) == 1679
        x1 = scale(x1,xdim-1,319);
        y1 = scale(y1,ydim-1,199);
        x2 = scale(x2,xdim-1,319);
        y2 = scale(y2,ydim-1,199);
    }

    horiz = clamp(horiz, HORIZ_MIN, HORIZ_MAX);

#ifdef USE_OPENGL
    oprojhacks = glprojectionhacks;
    glprojectionhacks = 0;
#endif
    {
        int32_t o = newaspect_enable;
        newaspect_enable = r_usenewaspect;
        setaspect_new_use_dimen = 1;

        setview(x1,y1,x2,y2);

        setaspect_new_use_dimen = 0;
        newaspect_enable = o;
    }

    G_DoInterpolations(smoothratio);

    G_HandleMirror(x, y, z, a, horiz, smoothratio);
#ifdef POLYMER
    if (getrendermode() == REND_POLYMER)
        polymer_setanimatesprites(G_DoSpriteAnimations, x,y,a,smoothratio);
#endif
    yax_preparedrawrooms();
    drawrooms(x,y,z,a,horiz,sect);
    yax_drawrooms(G_DoSpriteAnimations, sect, 0, smoothratio);

    display_mirror = 2;
    G_DoSpriteAnimations(x,y,a,smoothratio);
    display_mirror = 0;
    drawmasks();
    G_RestoreInterpolations();
    G_UpdateScreenArea();
#ifdef USE_OPENGL
    glprojectionhacks = oprojhacks;
#endif
}

#if !defined LUNATIC
GAMEEXEC_STATIC void VM_Execute(int loop)
{
    int tw = *insptr;
    DukePlayer_t * const ps = vm.g_pp;

    // jump directly into the loop, saving us from the checks during the first iteration
    goto skip_check;

    while (loop)
    {
        if (vm.g_flags & (VM_RETURN | VM_KILL | VM_NOEXECUTE))
            break;

        tw = *insptr;

skip_check:
        //      Bsprintf(g_szBuf,"Parsing: %d",*insptr);
        //      AddLog(g_szBuf);

        g_errorLineNum = tw>>12;
        g_tw = tw &= VM_INSTMASK;

        if (tw == CON_LEFTBRACE)
        {
            insptr++;
            loop++;
            continue;
        }
        else if (tw == CON_RIGHTBRACE)
        {
            insptr++;
            loop--;
            continue;
        }
        else if (tw == CON_ELSE)
        {
            insptr = (intptr_t *) *(insptr+1);
            continue;
        }
        else if (tw == CON_STATE)
        {
            intptr_t const * const tempscrptr = insptr + 2;
            insptr = (intptr_t *)*(insptr + 1);
            VM_Execute(1);
            insptr = tempscrptr;
            continue;
        }

        switch (tw)
        {
        case CON_IFVARE:
            insptr++;
            tw = Gv_GetVarX(*insptr++);
            VM_CONDITIONAL(tw == *insptr);
            continue;

        case CON_REDEFINEQUOTE:
            insptr++;
            {
                int32_t q = *insptr++, i = *insptr++;
                if (EDUKE32_PREDICT_FALSE((ScriptQuotes[q] == NULL || ScriptQuoteRedefinitions[i] == NULL)))
                {
                    CON_ERRPRINTF("%d %d null quote\n", q,i);
                    break;
                }
                Bstrcpy(ScriptQuotes[q],ScriptQuoteRedefinitions[i]);
                continue;
            }

        case CON_GETTHISPROJECTILE:
        case CON_SETTHISPROJECTILE:
            insptr++;
            {
                // syntax [gs]etplayer[<var>].x <VAR>
                // <varid> <xxxid> <varid>
                int32_t lVar1=*insptr++, lLabelID=*insptr++, lVar2=*insptr++;

                VM_AccessActiveProjectile(tw==CON_SETTHISPROJECTILE,lVar1,lLabelID,lVar2);
                continue;
            }

        case CON_IFRND:
            VM_CONDITIONAL(rnd(*(++insptr)));
            continue;

        case CON_IFCANSHOOTTARGET:
        {
            if (vm.g_x > 1024)
            {
                int16_t temphit;

                if ((tw = A_CheckHitSprite(vm.g_i, &temphit)) == (1 << 30))
                {
                    VM_CONDITIONAL(1);
                    continue;
                }

                int32_t sclip = 768, angdif = 16;

                if (A_CheckEnemySprite(vm.g_sp) && vm.g_sp->xrepeat > 56)
                {
                    sclip = 3084;
                    angdif = 48;
                }

#define CHECK(x) if (x >= 0 && sprite[x].picnum == vm.g_sp->picnum) { VM_CONDITIONAL(0); continue; }
#define CHECK2(x) do { vm.g_sp->ang += x; tw = A_CheckHitSprite(vm.g_i, &temphit); vm.g_sp->ang -= x; } while(0)

                if (tw > sclip)
                {
                    CHECK(temphit);
                    CHECK2(angdif);

                    if (tw > sclip)
                    {
                        CHECK(temphit);
                        CHECK2(-angdif);

                        if (tw > 768)
                        {
                            CHECK(temphit);
                            VM_CONDITIONAL(1);
                            continue;
                        }
                    }
                }
            }
            VM_CONDITIONAL(1);
        }
        continue;

        case CON_IFCANSEETARGET:
            tw = cansee(vm.g_sp->x, vm.g_sp->y, vm.g_sp->z-((krand()&41)<<8),
                               vm.g_sp->sectnum, ps->pos.x, ps->pos.y,
                               ps->pos.z/*-((krand()&41)<<8)*/, sprite[ps->i].sectnum);
            VM_CONDITIONAL(tw);
            if (tw) actor[vm.g_i].timetosleep = SLEEPTIME;
        continue;

        case CON_IFACTORNOTSTAYPUT:
            VM_CONDITIONAL(actor[vm.g_i].actorstayput == -1);
            continue;

        case CON_IFCANSEE:
        {
            tspritetype *s = (tspritetype *)&sprite[ps->i];

            // select sprite for monster to target
            // if holoduke is on, let them target holoduke first.
            //
            if (ps->holoduke_on >= 0)
            {
                s = (tspritetype *)&sprite[ps->holoduke_on];
                tw = cansee(vm.g_sp->x,vm.g_sp->y,vm.g_sp->z-(krand()&((32<<8)-1)),vm.g_sp->sectnum,
                           s->x,s->y,s->z,s->sectnum);

                if (tw == 0)
                {
                    // they can't see player's holoduke
                    // check for player...
                    s = (tspritetype *)&sprite[ps->i];
                }
            }

            // can they see player, (or player's holoduke)
            tw = cansee(vm.g_sp->x,vm.g_sp->y,vm.g_sp->z-(krand()&((47<<8))),vm.g_sp->sectnum,
                       s->x,s->y,s->z-(24<<8),s->sectnum);

            if (tw == 0)
            {
                // search around for target player

                // also modifies 'target' x&y if found..

                tw = 1;
                if (A_FurthestVisiblePoint(vm.g_i,s,&actor[vm.g_i].lastvx,&actor[vm.g_i].lastvy) == -1)
                    tw = 0;
            }
            else
            {
                // else, they did see it.
                // save where we were looking...
                actor[vm.g_i].lastvx = s->x;
                actor[vm.g_i].lastvy = s->y;
            }

            if (tw && (vm.g_sp->statnum == STAT_ACTOR || vm.g_sp->statnum == STAT_STANDABLE))
                actor[vm.g_i].timetosleep = SLEEPTIME;

            VM_CONDITIONAL(tw);
            continue;
        }

        case CON_IFHITWEAPON:
            VM_CONDITIONAL(A_IncurDamage(vm.g_i) >= 0);
            continue;

        case CON_IFSQUISHED:
            VM_CONDITIONAL(VM_CheckSquished());
            continue;

        case CON_IFDEAD:
            VM_CONDITIONAL(vm.g_sp->extra <= 0);
            continue;

        case CON_AI:
            insptr++;
            //Following changed to use pointersizes
            AC_AI_ID(vm.g_t) = *insptr++; // Ai

            AC_ACTION_ID(vm.g_t) = *(script + AC_AI_ID(vm.g_t));  // Action

            // NOTE: "if" check added in r1155. It used to be a pointer though.
            if (AC_AI_ID(vm.g_t))
                AC_MOVE_ID(vm.g_t) = *(script + AC_AI_ID(vm.g_t) + 1);  // move

            vm.g_sp->hitag = *(script + AC_AI_ID(vm.g_t) + 2);  // move flags

            AC_COUNT(vm.g_t) = AC_ACTION_COUNT(vm.g_t) = AC_CURFRAME(vm.g_t) = 0;

            if (!A_CheckEnemySprite(vm.g_sp) || vm.g_sp->extra > 0) // hack
                if (vm.g_sp->hitag&random_angle)
                    vm.g_sp->ang = krand()&2047;
            continue;

        case CON_ACTION:
            insptr++;
            AC_ACTION_COUNT(vm.g_t) = AC_CURFRAME(vm.g_t) = 0;
            AC_ACTION_ID(vm.g_t) = *insptr++;
            continue;

        case CON_IFPLAYERSL:
            VM_CONDITIONAL(numplayers < *(++insptr));
            continue;

        case CON_IFPDISTL:
            VM_CONDITIONAL(vm.g_x < *(++insptr));
            if (vm.g_x > MAXSLEEPDIST && actor[vm.g_i].timetosleep == 0)
                actor[vm.g_i].timetosleep = SLEEPTIME;
            continue;

        case CON_IFPDISTG:
            VM_CONDITIONAL(vm.g_x > *(++insptr));
            if (vm.g_x > MAXSLEEPDIST && actor[vm.g_i].timetosleep == 0)
                actor[vm.g_i].timetosleep = SLEEPTIME;
            continue;

        case CON_ADDSTRENGTH:
            insptr++;
            vm.g_sp->extra += *insptr++;
            continue;

        case CON_STRENGTH:
            insptr++;
            vm.g_sp->extra = *insptr++;
            continue;

        case CON_IFGOTWEAPONCE:
            insptr++;

            if ((GametypeFlags[ud.coop]&GAMETYPE_WEAPSTAY) && (g_netServer || ud.multimode > 1))
            {
                if (*insptr == 0)
                {
                    int32_t j = 0;
                    for (; j < ps->weapreccnt; j++)
                        if (ps->weaprecs[j] == vm.g_sp->picnum)
                            break;

                    VM_CONDITIONAL(j < ps->weapreccnt && vm.g_sp->owner == vm.g_i);
                    continue;
                }
                else if (ps->weapreccnt < MAX_WEAPONS)
                {
                    ps->weaprecs[ps->weapreccnt++] = vm.g_sp->picnum;
                    VM_CONDITIONAL(vm.g_sp->owner == vm.g_i);
                    continue;
                }
            }
            VM_CONDITIONAL(0);
            continue;

        case CON_GETLASTPAL:
            insptr++;
            if (vm.g_sp->picnum == APLAYER)
                vm.g_sp->pal = g_player[P_GetP(vm.g_sp)].ps->palookup;
            else
            {
                if (vm.g_sp->pal == 1 && vm.g_sp->extra == 0) // hack for frozen
                    vm.g_sp->extra++;
                vm.g_sp->pal = actor[vm.g_i].tempang;
            }
            actor[vm.g_i].tempang = 0;
            continue;

        case CON_TOSSWEAPON:
            insptr++;
            // NOTE: assumes that current actor is APLAYER
            P_DropWeapon(P_GetP(vm.g_sp));
            continue;

        case CON_MIKESND:
            insptr++;
            if (EDUKE32_PREDICT_FALSE(((unsigned)vm.g_sp->yvel >= MAXSOUNDS)))
            {
                CON_ERRPRINTF("Invalid sound %d\n", TrackerCast(vm.g_sp->yvel));
                continue;
            }
            if (!S_CheckSoundPlaying(vm.g_i,vm.g_sp->yvel))
                A_PlaySound(vm.g_sp->yvel,vm.g_i);
            continue;

        case CON_PKICK:
            insptr++;

            if ((g_netServer || ud.multimode > 1) && vm.g_sp->picnum == APLAYER)
            {
                if (g_player[otherp].ps->quick_kick == 0)
                    g_player[otherp].ps->quick_kick = 14;
            }
            else if (vm.g_sp->picnum != APLAYER && ps->quick_kick == 0)
                ps->quick_kick = 14;
            continue;

        case CON_SIZETO:
            insptr++;

            tw = (*insptr++ - vm.g_sp->xrepeat)<<1;
            vm.g_sp->xrepeat += ksgn(tw);

            if ((vm.g_sp->picnum == APLAYER && vm.g_sp->yrepeat < 36) || *insptr < vm.g_sp->yrepeat ||
                    ((vm.g_sp->yrepeat*(tilesiz[vm.g_sp->picnum].y+8))<<2) < (actor[vm.g_i].floorz - actor[vm.g_i].ceilingz))
            {
                tw = ((*insptr)-vm.g_sp->yrepeat)<<1;
                if (klabs(tw)) vm.g_sp->yrepeat += ksgn(tw);
            }

            insptr++;

            continue;

        case CON_SIZEAT:
            insptr++;
            vm.g_sp->xrepeat = (uint8_t) *insptr++;
            vm.g_sp->yrepeat = (uint8_t) *insptr++;
            continue;

        case CON_SHOOT:
            insptr++;
            A_Shoot(vm.g_i,*insptr++);
            continue;

        case CON_SOUNDONCE:
            if (EDUKE32_PREDICT_FALSE((unsigned)*(++insptr) >= MAXSOUNDS))
            {
                CON_ERRPRINTF("Invalid sound %d\n", (int32_t)*insptr++);
                continue;
            }
            if (!S_CheckSoundPlaying(vm.g_i,*insptr++))
                A_PlaySound(*(insptr-1),vm.g_i);
            continue;

        case CON_IFACTORSOUND:
            insptr++;
            {
                int32_t i = Gv_GetVarX(*insptr++), j = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)j >= MAXSOUNDS))
                {
                    CON_ERRPRINTF("Invalid sound %d\n", j);
                    insptr++;
                    continue;
                }
                insptr--;
                VM_CONDITIONAL(A_CheckSoundPlaying(i,j));
            }
            continue;

        case CON_IFSOUND:
            if (EDUKE32_PREDICT_FALSE((unsigned)*(++insptr) >= MAXSOUNDS))
            {
                CON_ERRPRINTF("Invalid sound %d\n", (int32_t)*insptr);
                insptr++;
                continue;
            }
            VM_CONDITIONAL(S_CheckSoundPlaying(vm.g_i,*insptr));
            //    VM_DoConditional(SoundOwner[*insptr][0].ow == vm.g_i);
            continue;

        case CON_STOPSOUND:
            if (EDUKE32_PREDICT_FALSE((unsigned)*(++insptr) >= MAXSOUNDS))
            {
                CON_ERRPRINTF("Invalid sound %d\n", (int32_t)*insptr);
                insptr++;
                continue;
            }
            if (S_CheckSoundPlaying(vm.g_i,*insptr))
                S_StopSound((int16_t)*insptr);
            insptr++;
            continue;

        case CON_STOPACTORSOUND:
            insptr++;
            {
                int32_t i = Gv_GetVarX(*insptr++), j = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)j>=MAXSOUNDS))
                {
                    CON_ERRPRINTF("Invalid sound %d\n", j);
                    continue;
                }

                if (A_CheckSoundPlaying(i,j))
                    S_StopEnvSound(j,i);

                continue;
            }

        case CON_SETACTORSOUNDPITCH:
            insptr++;
            {
                int32_t i = Gv_GetVarX(*insptr++), j = Gv_GetVarX(*insptr++), pitchoffset = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)j>=MAXSOUNDS))
                {
                    CON_ERRPRINTF("Invalid sound %d\n", j);
                    continue;
                }

                S_ChangeSoundPitch(j,i,pitchoffset);

                continue;
            }

        case CON_GLOBALSOUND:
            if (EDUKE32_PREDICT_FALSE((unsigned)*(++insptr) >= MAXSOUNDS))
            {
                CON_ERRPRINTF("Invalid sound %d\n", (int32_t)*insptr);
                insptr++;
                continue;
            }
            if (vm.g_p == screenpeek || (GametypeFlags[ud.coop]&GAMETYPE_COOPSOUND)
#ifdef SPLITSCREEN_MOD_HACKS
                || (g_fakeMultiMode==2)
#endif
                )
                A_PlaySound(*insptr,g_player[screenpeek].ps->i);
            insptr++;
            continue;

        case CON_SOUND:
            if (EDUKE32_PREDICT_FALSE((unsigned)*(++insptr) >= MAXSOUNDS))
            {
                CON_ERRPRINTF("Invalid sound %d\n", (int32_t)*insptr);
                insptr++;
                continue;
            }
            A_PlaySound(*insptr++,vm.g_i);
            continue;

        case CON_TIP:
            insptr++;
            ps->tipincs = GAMETICSPERSEC;
            continue;

        case CON_FALL:
            insptr++;
            VM_Fall(vm.g_i, vm.g_sp);
            continue;

        case CON_RETURN:
            vm.g_flags |= VM_RETURN;
        case CON_ENDA:
        case CON_BREAK:
        case CON_ENDS:
            return;
        case CON_NULLOP:
            insptr++;
            continue;

        case CON_ADDAMMO:
            insptr++;
            {
                int32_t weap=*insptr++, amount=*insptr++;

                if (EDUKE32_PREDICT_FALSE((unsigned)weap >= MAX_WEAPONS))
                {
                    CON_ERRPRINTF("Invalid weapon ID %d\n", weap);
                    break;
                }

                if (ps->ammo_amount[weap] >= ps->max_ammo_amount[weap])
                {
                    vm.g_flags |= VM_NOEXECUTE;
                    return;
                }

                P_AddWeaponAmmoCommon(ps, weap, amount);

                continue;
            }

        case CON_MONEY:
            insptr++;
            A_SpawnMultiple(vm.g_i, MONEY, *insptr++);
            continue;

        case CON_MAIL:
            insptr++;
            A_SpawnMultiple(vm.g_i, MAIL, *insptr++);
            continue;

        case CON_SLEEPTIME:
            insptr++;
            actor[vm.g_i].timetosleep = (int16_t)*insptr++;
            continue;

        case CON_PAPER:
            insptr++;
            A_SpawnMultiple(vm.g_i, PAPER, *insptr++);
            continue;

        case CON_ADDKILLS:
            insptr++;
            ps->actors_killed += *insptr++;
            actor[vm.g_i].actorstayput = -1;
            continue;

        case CON_LOTSOFGLASS:
            insptr++;
            A_SpawnGlass(vm.g_i,*insptr++);
            continue;

        case CON_KILLIT:
            insptr++;
            vm.g_flags |= VM_KILL;
            return;

        case CON_ADDWEAPON:
            insptr++;
            {
                int32_t weap=*insptr++, amount=*insptr++;
                VM_AddWeapon(weap, amount, ps);

                continue;
            }

        case CON_DEBUG:
            insptr++;
            initprintf("%" PRIdPTR "\n",*insptr++);
            continue;

        case CON_ENDOFGAME:
        case CON_ENDOFLEVEL:
            insptr++;
            ps->timebeforeexit = *insptr++;
            ps->customexitsound = -1;
            ud.eog = 1;
            continue;

        case CON_ADDPHEALTH:
            insptr++;

            {
                int32_t j;

                if (ps->newowner >= 0)
                    G_ClearCameraView(ps);

                j = sprite[ps->i].extra;

                if (vm.g_sp->picnum != ATOMICHEALTH)
                {
                    if (j > ps->max_player_health && *insptr > 0)
                    {
                        insptr++;
                        continue;
                    }
                    else
                    {
                        if (j > 0)
                            j += *insptr;
                        if (j > ps->max_player_health && *insptr > 0)
                            j = ps->max_player_health;
                    }
                }
                else
                {
                    if (j > 0)
                        j += *insptr;
                    if (j > (ps->max_player_health<<1))
                        j = (ps->max_player_health<<1);
                }

                if (j < 0) j = 0;

                if (ud.god == 0)
                {
                    if (*insptr > 0)
                    {
                        if ((j - *insptr) < (ps->max_player_health>>2) &&
                                j >= (ps->max_player_health>>2))
                            A_PlaySound(DUKE_GOTHEALTHATLOW,ps->i);

                        ps->last_extra = j;
                    }

                    sprite[ps->i].extra = j;
                }
            }

            insptr++;
            continue;

        case CON_MOVE:
            insptr++;
            AC_COUNT(vm.g_t) = 0;
            AC_MOVE_ID(vm.g_t) = *insptr++;
            vm.g_sp->hitag = *insptr++;
            if (A_CheckEnemySprite(vm.g_sp) && vm.g_sp->extra <= 0) // hack
                continue;
            if (vm.g_sp->hitag&random_angle)
                vm.g_sp->ang = krand()&2047;
            continue;

        case CON_ADDWEAPONVAR:
            insptr++;
            {
                int32_t weap=Gv_GetVarX(*insptr++), amount=Gv_GetVarX(*insptr++);
                VM_AddWeapon(weap, amount, ps);
                continue;
            }

        case CON_ACTIVATEBYSECTOR:
        case CON_OPERATESECTORS:
        case CON_OPERATEACTIVATORS:
        case CON_SETASPECT:
        case CON_SSP:
            insptr++;
            {
                int32_t var1 = Gv_GetVarX(*insptr++), var2;
                if (tw == CON_OPERATEACTIVATORS && *insptr == g_iThisActorID)
                {
                    var2 = vm.g_p;
                    insptr++;
                }
                else var2 = Gv_GetVarX(*insptr++);

                switch (tw)
                {
                case CON_ACTIVATEBYSECTOR:
                    if (EDUKE32_PREDICT_FALSE((unsigned)var1 >= (unsigned)numsectors))
                    {
                        CON_ERRPRINTF("Invalid sector %d\n", var1);
                        break;
                    }
                    G_ActivateBySector(var1, var2);
                    break;
                case CON_OPERATESECTORS:
                    if (EDUKE32_PREDICT_FALSE((unsigned)var1 >= (unsigned)numsectors))
                    {
                        CON_ERRPRINTF("Invalid sector %d\n", var1);
                        break;
                    }
                    G_OperateSectors(var1, var2);
                    break;
                case CON_OPERATEACTIVATORS:
                    if (EDUKE32_PREDICT_FALSE((unsigned)var2>=(unsigned)playerswhenstarted))
                    {
                        CON_ERRPRINTF("Invalid player %d\n", var2);
                        break;
                    }
                    G_OperateActivators(var1, var2);
                    break;
                case CON_SETASPECT:
                    setaspect(var1, var2);
                    break;
                case CON_SSP:
                    if (EDUKE32_PREDICT_FALSE((unsigned)var1 >= MAXSPRITES))
                    {
                        CON_ERRPRINTF("Invalid sprite %d\n", var1);
                        break;
                    }
                    A_SetSprite(var1, var2);
                    break;
                }
                continue;
            }

        case CON_CANSEESPR:
            insptr++;
            {
                int32_t lVar1 = Gv_GetVarX(*insptr++), lVar2 = Gv_GetVarX(*insptr++), res;

                if (EDUKE32_PREDICT_FALSE((unsigned)lVar1 >= MAXSPRITES || (unsigned)lVar2 >= MAXSPRITES))
                {
                    CON_ERRPRINTF("Invalid sprite %d\n", (unsigned)lVar1 >= MAXSPRITES ? lVar1 : lVar2);
                    res=0;
                }
                else res=cansee(sprite[lVar1].x,sprite[lVar1].y,sprite[lVar1].z,sprite[lVar1].sectnum,
                                    sprite[lVar2].x,sprite[lVar2].y,sprite[lVar2].z,sprite[lVar2].sectnum);

                Gv_SetVarX(*insptr++, res);
                continue;
            }

        case CON_OPERATERESPAWNS:
            insptr++;
            G_OperateRespawns(Gv_GetVarX(*insptr++));
            continue;

        case CON_OPERATEMASTERSWITCHES:
            insptr++;
            G_OperateMasterSwitches(Gv_GetVarX(*insptr++));
            continue;

        case CON_CHECKACTIVATORMOTION:
            insptr++;
            aGameVars[g_iReturnVarID].val.lValue = G_CheckActivatorMotion(Gv_GetVarX(*insptr++));
            continue;

        case CON_INSERTSPRITEQ:
            insptr++;
            A_AddToDeleteQueue(vm.g_i);
            continue;

        case CON_QSTRLEN:
            insptr++;
            {
                int32_t i=*insptr++;
                int32_t j=Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE(ScriptQuotes[j] == NULL))
                {
                    CON_ERRPRINTF("null quote %d\n", j);
                    Gv_SetVarX(i,-1);
                    continue;
                }
                Gv_SetVarX(i,Bstrlen(ScriptQuotes[j]));
                continue;
            }

        case CON_QSTRDIM:
            insptr++;
            {
                vec2_t dim = { 0, 0, };

                int32_t w=*insptr++;
                int32_t h=*insptr++;

                int32_t tilenum = Gv_GetVarX(*insptr++);
                int32_t x=Gv_GetVarX(*insptr++), y=Gv_GetVarX(*insptr++), z = Gv_GetVarX(*insptr++);
                int32_t blockangle=Gv_GetVarX(*insptr++);
                int32_t q=Gv_GetVarX(*insptr++);
                int32_t orientation=Gv_GetVarX(*insptr++);
                int32_t xspace=Gv_GetVarX(*insptr++), yline=Gv_GetVarX(*insptr++);
                int32_t xbetween=Gv_GetVarX(*insptr++), ybetween=Gv_GetVarX(*insptr++);
                int32_t f=Gv_GetVarX(*insptr++);
                int32_t x1=Gv_GetVarX(*insptr++), y1=Gv_GetVarX(*insptr++);
                int32_t x2=Gv_GetVarX(*insptr++), y2=Gv_GetVarX(*insptr++);

                orientation &= (ROTATESPRITE_MAX-1);

                if (EDUKE32_PREDICT_FALSE(tilenum < 0 || tilenum+255 >= MAXTILES))
                    CON_ERRPRINTF("invalid base tilenum %d\n", tilenum);
                else if (EDUKE32_PREDICT_FALSE((unsigned)q >= MAXQUOTES || ScriptQuotes[q] == NULL))
                    CON_ERRPRINTF("invalid quote ID %d\n", q);
                else
                    dim = G_ScreenTextSize(tilenum,x,y,z,blockangle,ScriptQuotes[q],2|orientation,xspace,yline,xbetween,ybetween,f,x1,y1,x2,y2);

                Gv_SetVarX(w,dim.x);
                Gv_SetVarX(h,dim.y);
                continue;
            }

        case CON_HEADSPRITESTAT:
            insptr++;
            {
                int32_t i=*insptr++;
                int32_t j=Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE((unsigned)j > MAXSTATUS))
                {
                    CON_ERRPRINTF("invalid status list %d\n", j);
                    continue;
                }
                Gv_SetVarX(i,headspritestat[j]);
                continue;
            }

        case CON_PREVSPRITESTAT:
            insptr++;
            {
                int32_t i=*insptr++;
                int32_t j=Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE((unsigned)j >= MAXSPRITES))
                {
                    CON_ERRPRINTF("invalid sprite ID %d\n", j);
                    continue;
                }
                Gv_SetVarX(i,prevspritestat[j]);
                continue;
            }

        case CON_NEXTSPRITESTAT:
            insptr++;
            {
                int32_t i=*insptr++;
                int32_t j=Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE((unsigned)j >= MAXSPRITES))
                {
                    CON_ERRPRINTF("invalid sprite ID %d\n", j);
                    continue;
                }
                Gv_SetVarX(i,nextspritestat[j]);
                continue;
            }

        case CON_HEADSPRITESECT:
            insptr++;
            {
                int32_t i=*insptr++;
                int32_t j=Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE((unsigned)j >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("invalid sector %d\n", j);
                    continue;
                }
                Gv_SetVarX(i,headspritesect[j]);
                continue;
            }

        case CON_PREVSPRITESECT:
            insptr++;
            {
                int32_t i=*insptr++;
                int32_t j=Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE((unsigned)j >= MAXSPRITES))
                {
                    CON_ERRPRINTF("invalid sprite ID %d\n", j);
                    continue;
                }
                Gv_SetVarX(i,prevspritesect[j]);
                continue;
            }

        case CON_NEXTSPRITESECT:
            insptr++;
            {
                int32_t i=*insptr++;
                int32_t j=Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE((unsigned)j >= MAXSPRITES))
                {
                    CON_ERRPRINTF("invalid sprite ID %d\n", j);
                    continue;
                }
                Gv_SetVarX(i,nextspritesect[j]);
                continue;
            }

        case CON_GETKEYNAME:
            insptr++;
            {
                int32_t i = Gv_GetVarX(*insptr++),
                        f = Gv_GetVarX(*insptr++);
                int32_t j = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)i >= MAXQUOTES || ScriptQuotes[i] == NULL))
                {
                    CON_ERRPRINTF("invalid quote ID %d\n", i);
                    continue;
                }
                else if (EDUKE32_PREDICT_FALSE((unsigned)f >= NUMGAMEFUNCTIONS))
                {
                    CON_ERRPRINTF("invalid function %d\n", f);
                    continue;
                }
                else
                {
                    if (j < 2)
                        Bstrcpy(tempbuf,KB_ScanCodeToString(ud.config.KeyboardKeys[f][j]));
                    else
                    {
                        Bstrcpy(tempbuf,KB_ScanCodeToString(ud.config.KeyboardKeys[f][0]));

                        if (!*tempbuf)
                            Bstrcpy(tempbuf,KB_ScanCodeToString(ud.config.KeyboardKeys[f][1]));
                    }
                }

                if (*tempbuf)
                    Bstrcpy(ScriptQuotes[i],tempbuf);

                continue;
            }

        case CON_QSUBSTR:
            insptr++;
            {
                int32_t q1 = Gv_GetVarX(*insptr++);
                int32_t q2 = Gv_GetVarX(*insptr++);
                int32_t st = Gv_GetVarX(*insptr++);
                int32_t ln = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)q1>=MAXQUOTES || ScriptQuotes[q1] == NULL))
                {
                    CON_ERRPRINTF("invalid quote ID %d\n", q1);
                    continue;
                }
                if (EDUKE32_PREDICT_FALSE((unsigned)q2>=MAXQUOTES || ScriptQuotes[q2] == NULL))
                {
                    CON_ERRPRINTF("invalid quote ID %d\n", q2);
                    continue;
                }

                if (EDUKE32_PREDICT_FALSE((unsigned)st >= MAXQUOTELEN))
                {
                    CON_ERRPRINTF("invalid start position %d\n", st);
                    continue;
                }

                if (EDUKE32_PREDICT_FALSE(ln < 0))
                {
                    CON_ERRPRINTF("invalid length %d\n", ln);
                    continue;
                }

                char *s1 = ScriptQuotes[q1];
                char *s2 = ScriptQuotes[q2];

                while (*s2 && st--) s2++;
                while ((*s1 = *s2) && ln--)
                {
                    s1++;
                    s2++;
                }
                *s1 = 0;

                continue;
            }

        case CON_GETPNAME:
        case CON_QSTRNCAT:
        case CON_QSTRCAT:
        case CON_QSTRCPY:
        case CON_QGETSYSSTR:
        case CON_CHANGESPRITESECT:
            insptr++;
            {
                int32_t i = Gv_GetVarX(*insptr++), j;
                if (tw == CON_GETPNAME && *insptr == g_iThisActorID)
                {
                    j = vm.g_p;
                    insptr++;
                }
                else j = Gv_GetVarX(*insptr++);

                switch (tw)
                {
                case CON_GETPNAME:
                    if (EDUKE32_PREDICT_FALSE((unsigned)i>=MAXQUOTES || ScriptQuotes[i] == NULL))
                    {
                        CON_ERRPRINTF("invalid quote ID %d\n", i);
                        break;
                    }
                    if (g_player[j].user_name[0])
                        Bstrcpy(ScriptQuotes[i],g_player[j].user_name);
                    else Bsprintf(ScriptQuotes[i],"%d",j);
                    break;
                case CON_QGETSYSSTR:
                    if (EDUKE32_PREDICT_FALSE((unsigned)i>=MAXQUOTES || ScriptQuotes[i] == NULL))
                    {
                        CON_ERRPRINTF("invalid quote ID %d\n", i);
                        break;
                    }
                    switch (j)
                    {
                    case STR_MAPNAME:
                    case STR_MAPFILENAME:
                    {
                        int32_t idx = ud.volume_number*MAXLEVELS + ud.level_number;
                        const char *src;

                        if (EDUKE32_PREDICT_FALSE((unsigned)idx >= ARRAY_SIZE(MapInfo)))
                        {
                            CON_ERRPRINTF("out of bounds map number (vol=%d, lev=%d)\n",
                                          ud.volume_number, ud.level_number);
                            break;
                        }

                        src = j==STR_MAPNAME ? MapInfo[idx].name : MapInfo[idx].filename;
                        if (EDUKE32_PREDICT_FALSE(src == NULL))
                        {
                            CON_ERRPRINTF("attempted access to %s of non-existent map (vol=%d, lev=%d)",
                                          j==STR_MAPNAME ? "name" : "file name",
                                          ud.volume_number, ud.level_number);
                            break;
                        }

                        Bstrcpy(ScriptQuotes[i], j==STR_MAPNAME ? MapInfo[idx].name : MapInfo[idx].filename);
                        break;
                    }
                    case STR_PLAYERNAME:
                        if (EDUKE32_PREDICT_FALSE((unsigned)vm.g_p >= (unsigned)playerswhenstarted))
                        {
                            CON_ERRPRINTF("Invalid player ID %d\n", vm.g_p);
                            break;
                        }
                        Bstrcpy(ScriptQuotes[i],g_player[vm.g_p].user_name);
                        break;
                    case STR_VERSION:
                        Bsprintf(tempbuf,HEAD2 " %s",s_buildRev);
                        Bstrcpy(ScriptQuotes[i],tempbuf);
                        break;
                    case STR_GAMETYPE:
                        Bstrcpy(ScriptQuotes[i],GametypeNames[ud.coop]);
                        break;
                    case STR_VOLUMENAME:
                        if (EDUKE32_PREDICT_FALSE((unsigned)ud.volume_number >= MAXVOLUMES))
                        {
                            CON_ERRPRINTF("invalid volume (%d)\n", ud.volume_number);
                            break;
                        }
                        Bstrcpy(ScriptQuotes[i],EpisodeNames[ud.volume_number]);
                        break;
                    case STR_YOURTIME:
                        Bstrcpy(ScriptQuotes[i],G_PrintYourTime());
                        break;
                    case STR_PARTIME:
                        Bstrcpy(ScriptQuotes[i],G_PrintParTime());
                        break;
                    case STR_DESIGNERTIME:
                        Bstrcpy(ScriptQuotes[i],G_PrintDesignerTime());
                        break;
                    case STR_BESTTIME:
                        Bstrcpy(ScriptQuotes[i],G_PrintBestTime());
                        break;
                    default:
                        CON_ERRPRINTF("unknown str ID %d %d\n", i,j);
                    }
                    break;
                case CON_QSTRCAT:
                    if (EDUKE32_PREDICT_FALSE(ScriptQuotes[i] == NULL || ScriptQuotes[j] == NULL)) goto nullquote;
                    Bstrncat(ScriptQuotes[i],ScriptQuotes[j],(MAXQUOTELEN-1)-Bstrlen(ScriptQuotes[i]));
                    break;
                case CON_QSTRNCAT:
                    if (EDUKE32_PREDICT_FALSE(ScriptQuotes[i] == NULL || ScriptQuotes[j] == NULL)) goto nullquote;
                    Bstrncat(ScriptQuotes[i],ScriptQuotes[j],Gv_GetVarX(*insptr++));
                    break;
                case CON_QSTRCPY:
                    if (EDUKE32_PREDICT_FALSE(ScriptQuotes[i] == NULL || ScriptQuotes[j] == NULL)) goto nullquote;
                    if (i != j)
                        Bstrcpy(ScriptQuotes[i],ScriptQuotes[j]);
                    break;
                case CON_CHANGESPRITESECT:
                    if (EDUKE32_PREDICT_FALSE((unsigned)i >= MAXSPRITES))
                    {
                        CON_ERRPRINTF("Invalid sprite %d\n", i);
                        break;
                    }
                    else if (EDUKE32_PREDICT_FALSE((unsigned)j >= (unsigned)numsectors))
                    {
                        CON_ERRPRINTF("Invalid sector %d\n", j);
                        break;
                    }
                    changespritesect(i,j);
                    break;
                default:
nullquote:
                    CON_ERRPRINTF("null quote %d\n", ScriptQuotes[i] ? j : i);
                    break;
                }
                continue;
            }

        case CON_CHANGESPRITESTAT:
            insptr++;
            {
                int32_t i = Gv_GetVarX(*insptr++);
                int32_t j = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)i >= MAXSPRITES))
                {
                    CON_ERRPRINTF("Invalid sprite: %d\n", i);
                    continue;
                }
                if (EDUKE32_PREDICT_FALSE((unsigned)j >= MAXSTATUS))
                {
                    CON_ERRPRINTF("Invalid statnum: %d\n", j);
                    continue;
                }
                if (sprite[i].statnum == j)
                    continue;

                /* initialize actor data when changing to an actor statnum because there's usually
                garbage left over from being handled as a hard coded object */


                if (sprite[i].statnum > STAT_ZOMBIEACTOR && (j == STAT_ACTOR || j == STAT_ZOMBIEACTOR))
                {
                    actor_t * const a = &actor[i];

                    a->lastvx = 0;
                    a->lastvy = 0;
                    a->timetosleep = 0;
                    a->cgg = 0;
                    a->movflag = 0;
                    a->tempang = 0;
                    a->dispicnum = 0;
                    T1=T2=T3=T4=T5=T6=T7=T8=T9=0;
                    a->flags = 0;
                    sprite[i].hitag = 0;

                    if (G_HaveActor(sprite[i].picnum))
                    {
                        const intptr_t *actorptr = g_tile[sprite[i].picnum].execPtr;
                        // offsets
                        AC_ACTION_ID(a->t_data) = actorptr[1];
                        AC_MOVE_ID(a->t_data) = actorptr[2];
                        AC_MOVFLAGS(&sprite[i], &actor[i]) = actorptr[3];  // ai bits (movflags)
                    }
                }

                changespritestat(i,j);
                continue;
            }

        case CON_STARTLEVEL:
            insptr++; // skip command
            {
                // from 'level' cheat in game.c (about line 6250)
                int32_t volnume=Gv_GetVarX(*insptr++), levnume=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)volnume >= MAXVOLUMES))
                {
                    CON_ERRPRINTF("invalid volume (%d)\n", volnume);
                    continue;
                }

                if (EDUKE32_PREDICT_FALSE((unsigned)levnume >= MAXLEVELS))
                {
                    CON_ERRPRINTF("invalid level (%d)\n", levnume);
                    continue;
                }

                ud.m_volume_number = ud.volume_number = volnume;
                ud.m_level_number = ud.level_number = levnume;
                //if (numplayers > 1 && g_netServer)
                //    Net_NewGame(volnume,levnume);
                //else
                {
                    g_player[myconnectindex].ps->gm |= MODE_EOL;
                    ud.display_bonus_screen = 0;
                } // MODE_RESTART;

                continue;
            }

        case CON_MYOSX:
        case CON_MYOSPALX:
        case CON_MYOS:
        case CON_MYOSPAL:
            insptr++;
            {
                int32_t x=Gv_GetVarX(*insptr++), y=Gv_GetVarX(*insptr++), tilenum=Gv_GetVarX(*insptr++);
                int32_t shade=Gv_GetVarX(*insptr++), orientation=Gv_GetVarX(*insptr++);

                switch (tw)
                {
                case CON_MYOS:
                    G_DrawTile(x,y,tilenum,shade,orientation);
                    break;
                case CON_MYOSPAL:
                {
                    int32_t pal=Gv_GetVarX(*insptr++);
                    G_DrawTilePal(x,y,tilenum,shade,orientation,pal);
                    break;
                }
                case CON_MYOSX:
                    G_DrawTileSmall(x,y,tilenum,shade,orientation);
                    break;
                case CON_MYOSPALX:
                {
                    int32_t pal=Gv_GetVarX(*insptr++);
                    G_DrawTilePalSmall(x,y,tilenum,shade,orientation,pal);
                    break;
                }
                }
                continue;
            }

        case CON_SWITCH:
            insptr++;
            {
                // command format:
                // variable ID to check
                // script offset to 'end'
                // count of case statements
                // script offset to default case (null if none)
                // For each case: value, ptr to code
                int32_t lValue = Gv_GetVarX(*insptr++), lEnd = *insptr++, lCases = *insptr++;
                intptr_t const *lpDefault = insptr++, *lpCases = insptr;
                int32_t lCheckCase, left = 0, right = lCases - 1;
                insptr += lCases << 1;

                do
                {
                    lCheckCase = (left + right) >> 1;

                    if (lpCases[lCheckCase << 1] > lValue)
                        right = lCheckCase - 1;
                    else if (lpCases[lCheckCase << 1] < lValue)
                        left = lCheckCase + 1;
                    else if (lpCases[lCheckCase << 1] == lValue)
                    {
                        // fake a 2-d Array
                        insptr = (intptr_t *)(lpCases[(lCheckCase << 1) + 1] + &script[0]);
                        VM_Execute(1);
                        goto matched;
                    }

                    if (right - left < 0)
                        break;
                }
                while (1);

                if (*lpDefault)
                {
                    insptr = (intptr_t *)(*lpDefault + &script[0]);
                    VM_Execute(1);
                }

            matched:
                insptr = (intptr_t *)(lEnd + (intptr_t)&script[0]);

                continue;
            }

        case CON_ENDSWITCH:
        case CON_ENDEVENT:
            insptr++;
            return;

        case CON_DISPLAYRAND:
            insptr++;
            Gv_SetVarX(*insptr++, system_15bit_rand());
            continue;

        case CON_DRAGPOINT:
            insptr++;
            {
                int32_t wallnum = Gv_GetVarX(*insptr++), newx = Gv_GetVarX(*insptr++), newy = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)wallnum >= (unsigned)numwalls))
                {
                    CON_ERRPRINTF("Invalid wall %d\n", wallnum);
                    continue;
                }

                dragpoint(wallnum,newx,newy,0);
                continue;
            }

        case CON_LDIST:
            insptr++;
            {
                int32_t distvar = *insptr++, xvar = Gv_GetVarX(*insptr++), yvar = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)xvar >= MAXSPRITES || (unsigned)yvar >= MAXSPRITES))
                {
                    CON_ERRPRINTF("invalid sprite %d %d\n", xvar, yvar);
                    continue;
                }

                Gv_SetVarX(distvar, ldist(&sprite[xvar],&sprite[yvar]));
                continue;
            }

        case CON_DIST:
            insptr++;
            {
                int32_t distvar = *insptr++, xvar = Gv_GetVarX(*insptr++), yvar = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)xvar >= MAXSPRITES || (unsigned)yvar >= MAXSPRITES))
                {
                    CON_ERRPRINTF("invalid sprite %d %d\n", xvar, yvar);
                    continue;
                }

                Gv_SetVarX(distvar, dist(&sprite[xvar],&sprite[yvar]));
                continue;
            }

        case CON_GETANGLE:
            insptr++;
            {
                int32_t angvar = *insptr++;
                int32_t xvar = Gv_GetVarX(*insptr++);
                int32_t yvar = Gv_GetVarX(*insptr++);

                Gv_SetVarX(angvar, getangle(xvar,yvar));
                continue;
            }

        case CON_GETINCANGLE:
            insptr++;
            {
                int32_t angvar = *insptr++;
                int32_t xvar = Gv_GetVarX(*insptr++);
                int32_t yvar = Gv_GetVarX(*insptr++);

                Gv_SetVarX(angvar, G_GetAngleDelta(xvar,yvar));
                continue;
            }

        case CON_MULSCALE:
            insptr++;
            {
                int32_t var1 = *insptr++, var2 = Gv_GetVarX(*insptr++);
                int32_t var3 = Gv_GetVarX(*insptr++), var4 = Gv_GetVarX(*insptr++);

                Gv_SetVarX(var1, mulscale(var2, var3, var4));
                continue;
            }

        case CON_INITTIMER:
            insptr++;
            G_InitTimer(Gv_GetVarX(*insptr++));
            continue;

        case CON_TIME:
            insptr += 2;
            continue;

        case CON_ESPAWNVAR:
        case CON_EQSPAWNVAR:
        case CON_QSPAWNVAR:
            insptr++;
            {
                int32_t lIn=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)vm.g_sp->sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", TrackerCast(vm.g_sp->sectnum));
                    continue;
                }
                int32_t j = A_Spawn(vm.g_i, lIn);

                switch (tw)
                {
                case CON_EQSPAWNVAR:
                    if (j != -1)
                        A_AddToDeleteQueue(j);
                case CON_ESPAWNVAR:
                    aGameVars[g_iReturnVarID].val.lValue = j;
                    break;

                case CON_QSPAWNVAR:
                    if (j != -1)
                        A_AddToDeleteQueue(j);
                    break;
                }
                continue;
            }

        case CON_ESPAWN:
        case CON_EQSPAWN:
        case CON_QSPAWN:
            insptr++;

            {
                if (EDUKE32_PREDICT_FALSE((unsigned)vm.g_sp->sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", TrackerCast(vm.g_sp->sectnum));
                    insptr++;
                    continue;
                }

                int32_t j = A_Spawn(vm.g_i,*insptr++);

                switch (tw)
                {
                case CON_EQSPAWN:
                    if (j != -1)
                        A_AddToDeleteQueue(j);
                case CON_ESPAWN:
                    aGameVars[g_iReturnVarID].val.lValue = j;
                    break;
                case CON_QSPAWN:
                    if (j != -1)
                        A_AddToDeleteQueue(j);
                    break;
                }
            }
            continue;

        case CON_ESHOOT:
        case CON_EZSHOOT:
        case CON_ZSHOOT:
            insptr++;
            {
                // NOTE: (int16_t) cast because we want to exclude that
                // SHOOT_HARDCODED_ZVEL is passed.
                const int32_t zvel = (tw == CON_ESHOOT) ?
                    SHOOT_HARDCODED_ZVEL : (int16_t)Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)vm.g_sp->sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", TrackerCast(vm.g_sp->sectnum));
                    insptr++;
                    continue;
                }

                int32_t j = A_ShootWithZvel(vm.g_i,*insptr++,zvel);

                if (tw != CON_ZSHOOT)
                    aGameVars[g_iReturnVarID].val.lValue = j;
            }
            continue;

        case CON_SHOOTVAR:
        case CON_ESHOOTVAR:
            insptr++;
            {
                int32_t j=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)vm.g_sp->sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", TrackerCast(vm.g_sp->sectnum));
                    continue;
                }

                j = A_Shoot(vm.g_i, j);
                if (tw == CON_ESHOOTVAR)
                    aGameVars[g_iReturnVarID].val.lValue = j;
                continue;
            }

        case CON_EZSHOOTVAR:
        case CON_ZSHOOTVAR:
            insptr++;
            {
                const int32_t zvel = (int16_t)Gv_GetVarX(*insptr++);
                int32_t j=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)vm.g_sp->sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", TrackerCast(vm.g_sp->sectnum));
                    continue;
                }

                j = A_ShootWithZvel(vm.g_i, j, zvel);
                if (tw == CON_EZSHOOTVAR)
                    aGameVars[g_iReturnVarID].val.lValue = j;
                continue;
            }

        case CON_CMENU:
            insptr++;
            M_ChangeMenu(Gv_GetVarX(*insptr++));
            continue;

        case CON_SOUNDVAR:
        case CON_STOPSOUNDVAR:
        case CON_SOUNDONCEVAR:
        case CON_GLOBALSOUNDVAR:
        case CON_SCREENSOUND:
            insptr++;
            {
                int32_t j=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)j>=MAXSOUNDS))
                {
                    CON_ERRPRINTF("Invalid sound %d\n", j);
                    continue;
                }

                switch (tw)
                {
                    case CON_SOUNDONCEVAR: // falls through to CON_SOUNDVAR
                        if (!S_CheckSoundPlaying(vm.g_i, j))
                    case CON_SOUNDVAR:
                        A_PlaySound((int16_t)j, vm.g_i);
                        continue;
                    case CON_GLOBALSOUNDVAR:
                        A_PlaySound((int16_t)j, g_player[screenpeek].ps->i);
                        continue;
                    case CON_STOPSOUNDVAR:
                        if (S_CheckSoundPlaying(vm.g_i, j))
                            S_StopSound((int16_t)j);
                        continue;
                    case CON_SCREENSOUND:
                        A_PlaySound(j, -1);
                        continue;
                }
            }
            continue;

        case CON_CUTSCENE:
        case CON_IFCUTSCENE:
            insptr++;
            {
                int32_t j = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)j >= MAXQUOTES || ScriptQuotes[j] == NULL))
                {
                    CON_ERRPRINTF("invalid quote ID %d for anim!\n", j);
                    continue;
                }

                if (tw == CON_IFCUTSCENE)
                {
                    VM_CONDITIONAL(g_animPtr == G_FindAnim(ScriptQuotes[j]));
                    continue;
                }

                tw = ps->palette;
                G_PlayAnim(ScriptQuotes[j]);
                P_SetGamePalette(ps, tw, 2 + 16);
                continue;
            }
            continue;

        case CON_GUNIQHUDID:
            insptr++;
            {
                tw = Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE((unsigned)tw >= MAXUNIQHUDID - 1))
                    CON_ERRPRINTF("Invalid ID %d\n", tw);
                else
                    guniqhudid = tw;

                continue;
            }

        case CON_SAVEGAMEVAR:
        case CON_READGAMEVAR:
        {
            int32_t i=0;
            insptr++;
            if (ud.config.scripthandle < 0)
            {
                insptr++;
                continue;
            }
            switch (tw)
            {
            case CON_SAVEGAMEVAR:
                i=Gv_GetVarX(*insptr);
                SCRIPT_PutNumber(ud.config.scripthandle, "Gamevars",aGameVars[*insptr++].szLabel,i,FALSE,FALSE);
                break;
            case CON_READGAMEVAR:
                SCRIPT_GetNumber(ud.config.scripthandle, "Gamevars",aGameVars[*insptr].szLabel,&i);
                Gv_SetVarX(*insptr++, i);
                break;
            }
            continue;
        }

        case CON_SHOWVIEW:
        case CON_SHOWVIEWUNBIASED:
            insptr++;
            {
                int32_t x=Gv_GetVarX(*insptr++);
                int32_t y=Gv_GetVarX(*insptr++);
                int32_t z=Gv_GetVarX(*insptr++);
                int32_t a=Gv_GetVarX(*insptr++);
                int32_t horiz=Gv_GetVarX(*insptr++);
                int32_t sect=Gv_GetVarX(*insptr++);
                int32_t x1=Gv_GetVarX(*insptr++);
                int32_t y1=Gv_GetVarX(*insptr++);
                int32_t x2=Gv_GetVarX(*insptr++);
                int32_t y2=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE(x1 < 0 || y1 < 0 || x2 >= 320 || y2 >= 200))
                {
                    CON_ERRPRINTF("incorrect coordinates\n");
                    continue;
                }

                if (EDUKE32_PREDICT_FALSE((unsigned)sect >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", sect);
                    continue;
                }

                G_ShowView(x, y, z, a, horiz, sect, x1, y1, x2, y2, (tw != CON_SHOWVIEW));

                continue;
            }

        case CON_ROTATESPRITEA:
        case CON_ROTATESPRITE16:
        case CON_ROTATESPRITE:
            insptr++;
            {
                int32_t x=Gv_GetVarX(*insptr++),   y=Gv_GetVarX(*insptr++),           z=Gv_GetVarX(*insptr++);
                int32_t a=Gv_GetVarX(*insptr++),   tilenum=Gv_GetVarX(*insptr++),     shade=Gv_GetVarX(*insptr++);
                int32_t pal=Gv_GetVarX(*insptr++), orientation=Gv_GetVarX(*insptr++);
                int32_t alpha = (tw == CON_ROTATESPRITEA) ? Gv_GetVarX(*insptr++) : 0;
                int32_t x1=Gv_GetVarX(*insptr++),  y1=Gv_GetVarX(*insptr++);
                int32_t x2=Gv_GetVarX(*insptr++),  y2=Gv_GetVarX(*insptr++);

                int32_t blendidx = 0;

                if (tw != CON_ROTATESPRITE16 && !(orientation&ROTATESPRITE_FULL16))
                {
                    x<<=16;
                    y<<=16;
                }

                if (EDUKE32_PREDICT_FALSE((unsigned)tilenum >= MAXTILES))
                {
                    CON_ERRPRINTF("invalid tilenum %d\n", tilenum);
                    continue;
                }

                if (EDUKE32_PREDICT_FALSE(x < -(320<<16) || x >= (640<<16) || y < -(200<<16) || y >= (400<<16)))
                {
                    CON_ERRPRINTF("invalid coordinates: %d, %d\n", x, y);
                    continue;
                }

                orientation &= (ROTATESPRITE_MAX-1);

                NEG_ALPHA_TO_BLEND(alpha, blendidx, orientation);

                rotatesprite_(x,y,z,a,tilenum,shade,pal,2|orientation,alpha,blendidx,x1,y1,x2,y2);
                continue;
            }

        case CON_GAMETEXT:
        case CON_GAMETEXTZ:
            insptr++;
            {
                int32_t tilenum = Gv_GetVarX(*insptr++);
                int32_t x=Gv_GetVarX(*insptr++), y=Gv_GetVarX(*insptr++), q=Gv_GetVarX(*insptr++);
                int32_t shade=Gv_GetVarX(*insptr++), pal=Gv_GetVarX(*insptr++);
                int32_t orientation=Gv_GetVarX(*insptr++);
                int32_t x1=Gv_GetVarX(*insptr++), y1=Gv_GetVarX(*insptr++);
                int32_t x2=Gv_GetVarX(*insptr++), y2=Gv_GetVarX(*insptr++);
                int32_t z = (tw == CON_GAMETEXTZ) ? Gv_GetVarX(*insptr++) : 65536;

                if (EDUKE32_PREDICT_FALSE(tilenum < 0 || tilenum+255 >= MAXTILES))
                {
                    CON_ERRPRINTF("invalid base tilenum %d\n", tilenum);
                    continue;
                }

                if (EDUKE32_PREDICT_FALSE((unsigned)q >= MAXQUOTES || ScriptQuotes[q] == NULL))
                {
                    CON_ERRPRINTF("invalid quote ID %d\n", q);
                    continue;
                }

                orientation &= (ROTATESPRITE_MAX-1);

                G_PrintGameText(0,tilenum,x>>1,y,ScriptQuotes[q],shade,pal,orientation,x1,y1,x2,y2,z,0);
                continue;
            }

        case CON_DIGITALNUMBER:
        case CON_DIGITALNUMBERZ:
            insptr++;
            {
                int32_t tilenum = Gv_GetVarX(*insptr++);
                int32_t x=Gv_GetVarX(*insptr++), y=Gv_GetVarX(*insptr++), q=Gv_GetVarX(*insptr++);
                int32_t shade=Gv_GetVarX(*insptr++), pal=Gv_GetVarX(*insptr++);
                int32_t orientation=Gv_GetVarX(*insptr++);
                int32_t x1=Gv_GetVarX(*insptr++), y1=Gv_GetVarX(*insptr++);
                int32_t x2=Gv_GetVarX(*insptr++), y2=Gv_GetVarX(*insptr++);
                int32_t z = (tw == CON_DIGITALNUMBERZ) ? Gv_GetVarX(*insptr++) : 65536;

                // NOTE: '-' not taken into account, but we have rotatesprite() bound check now anyway
                if (EDUKE32_PREDICT_FALSE(tilenum < 0 || tilenum+9 >= MAXTILES))
                {
                    CON_ERRPRINTF("invalid base tilenum %d\n", tilenum);
                    continue;
                }

                G_DrawTXDigiNumZ(tilenum,x,y,q,shade,pal,orientation,x1,y1,x2,y2,z);
                continue;
            }

        case CON_MINITEXT:
            insptr++;
            {
                int32_t x=Gv_GetVarX(*insptr++), y=Gv_GetVarX(*insptr++), q=Gv_GetVarX(*insptr++);
                int32_t shade=Gv_GetVarX(*insptr++), pal=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)q >= MAXQUOTES || ScriptQuotes[q] == NULL))
                {
                    CON_ERRPRINTF("invalid quote ID %d\n", q);
                    continue;
                }

                minitextshade(x,y,ScriptQuotes[q],shade,pal, 2+8+16);
                continue;
            }

        case CON_SCREENTEXT:
            insptr++;
            {
                int32_t tilenum = Gv_GetVarX(*insptr++);
                int32_t x=Gv_GetVarX(*insptr++), y=Gv_GetVarX(*insptr++), z = Gv_GetVarX(*insptr++);
                int32_t blockangle=Gv_GetVarX(*insptr++), charangle=Gv_GetVarX(*insptr++);
                int32_t q=Gv_GetVarX(*insptr++);
                int32_t shade=Gv_GetVarX(*insptr++), pal=Gv_GetVarX(*insptr++);
                int32_t orientation=Gv_GetVarX(*insptr++);
                int32_t alpha=Gv_GetVarX(*insptr++);
                int32_t xspace=Gv_GetVarX(*insptr++), yline=Gv_GetVarX(*insptr++);
                int32_t xbetween=Gv_GetVarX(*insptr++), ybetween=Gv_GetVarX(*insptr++);
                int32_t f=Gv_GetVarX(*insptr++);
                int32_t x1=Gv_GetVarX(*insptr++), y1=Gv_GetVarX(*insptr++);
                int32_t x2=Gv_GetVarX(*insptr++), y2=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE(tilenum < 0 || tilenum+255 >= MAXTILES))
                {
                    CON_ERRPRINTF("invalid base tilenum %d\n", tilenum);
                    continue;
                }

                if (EDUKE32_PREDICT_FALSE((unsigned)q >= MAXQUOTES || ScriptQuotes[q] == NULL))
                {
                    CON_ERRPRINTF("invalid quote ID %d\n", q);
                    continue;
                }

                orientation &= (ROTATESPRITE_MAX-1);

                G_ScreenText(tilenum,x,y,z,blockangle,charangle,ScriptQuotes[q],shade,pal,2|orientation,alpha,xspace,yline,xbetween,ybetween,f,x1,y1,x2,y2);
                continue;
            }

        case CON_ANGOFF:
            insptr++;
            spriteext[vm.g_i].angoff=*insptr++;
            continue;

        case CON_GETZRANGE:
            insptr++;
            {
                vec3_t vect;

                vect.x = Gv_GetVarX(*insptr++);
                vect.y = Gv_GetVarX(*insptr++);
                vect.z = Gv_GetVarX(*insptr++);

                int32_t sectnum = Gv_GetVarX(*insptr++);
                int32_t ceilzvar = *insptr++, ceilhitvar = *insptr++, florzvar = *insptr++, florhitvar = *insptr++;
                int32_t walldist = Gv_GetVarX(*insptr++), clipmask = Gv_GetVarX(*insptr++);
                int32_t ceilz, ceilhit, florz, florhit;

                if (EDUKE32_PREDICT_FALSE((unsigned)sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", sectnum);
                    continue;
                }

                getzrange(&vect, sectnum, &ceilz, &ceilhit, &florz, &florhit, walldist, clipmask);
                Gv_SetVarX(ceilzvar, ceilz);
                Gv_SetVarX(ceilhitvar, ceilhit);
                Gv_SetVarX(florzvar, florz);
                Gv_SetVarX(florhitvar, florhit);

                continue;
            }

        case CON_SECTSETINTERPOLATION:
        case CON_SECTCLEARINTERPOLATION:
            insptr++;
            {
                int32_t sectnum = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", sectnum);
                    continue;
                }

                if (tw==CON_SECTSETINTERPOLATION)
                    Sect_SetInterpolation(sectnum);
                else
                    Sect_ClearInterpolation(sectnum);

                continue;
            }

        case CON_CALCHYPOTENUSE:
            insptr++;
            {
                int32_t retvar=*insptr++;
                int64_t dax=Gv_GetVarX(*insptr++), day=Gv_GetVarX(*insptr++);
                int64_t hypsq = dax*dax + day*day;

                if (hypsq > (int64_t)INT32_MAX)
                    Gv_SetVarX(retvar, (int32_t)sqrt((double)hypsq));
                else
                    Gv_SetVarX(retvar, ksqrt((uint32_t)hypsq));

                continue;
            }

        case CON_LINEINTERSECT:
        case CON_RAYINTERSECT:
            insptr++;
            {
                int32_t x1=Gv_GetVarX(*insptr++), y1=Gv_GetVarX(*insptr++), z1=Gv_GetVarX(*insptr++);
                int32_t x2=Gv_GetVarX(*insptr++), y2=Gv_GetVarX(*insptr++), z2=Gv_GetVarX(*insptr++);
                int32_t x3=Gv_GetVarX(*insptr++), y3=Gv_GetVarX(*insptr++), x4=Gv_GetVarX(*insptr++), y4=Gv_GetVarX(*insptr++);
                int32_t intxvar=*insptr++, intyvar=*insptr++, intzvar=*insptr++, retvar=*insptr++;
                int32_t intx, inty, intz, ret;

                if (tw==CON_LINEINTERSECT)
                    ret = lintersect(x1, y1, z1, x2, y2, z2, x3, y3, x4, y4, &intx, &inty, &intz);
                else
                    ret = rayintersect(x1, y1, z1, x2, y2, z2, x3, y3, x4, y4, &intx, &inty, &intz);

                Gv_SetVarX(retvar, ret);
                if (ret)
                {
                    Gv_SetVarX(intxvar, intx);
                    Gv_SetVarX(intyvar, inty);
                    Gv_SetVarX(intzvar, intz);
                }

                continue;
            }

        case CON_CLIPMOVE:
        case CON_CLIPMOVENOSLIDE:
            insptr++;
            {
                int32_t retvar=*insptr++, xvar=*insptr++, yvar=*insptr++, z=Gv_GetVarX(*insptr++), sectnumvar=*insptr++;
                int32_t xvect=Gv_GetVarX(*insptr++), yvect=Gv_GetVarX(*insptr++);
                int32_t walldist=Gv_GetVarX(*insptr++), floordist=Gv_GetVarX(*insptr++), ceildist=Gv_GetVarX(*insptr++);
                int32_t clipmask=Gv_GetVarX(*insptr++);
                int16_t sectnum;

                vec3_t vect;
                vect.x = Gv_GetVarX(xvar);
                vect.y = Gv_GetVarX(yvar);
                vect.z = z;
                sectnum = Gv_GetVarX(sectnumvar);

                if (EDUKE32_PREDICT_FALSE((unsigned)sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", sectnum);
                    Gv_SetVarX(retvar, 0);
                    continue;
                }

                Gv_SetVarX(retvar, clipmovex(&vect, &sectnum, xvect, yvect, walldist, floordist, ceildist,
                                             clipmask, (tw==CON_CLIPMOVENOSLIDE)));
                Gv_SetVarX(sectnumvar, sectnum);
                Gv_SetVarX(xvar, vect.x);
                Gv_SetVarX(yvar, vect.y);

                continue;
            }

        case CON_HITSCAN:
            insptr++;
            {
                vec3_t vect;

                vect.x = Gv_GetVarX(*insptr++);
                vect.y = Gv_GetVarX(*insptr++);
                vect.z = Gv_GetVarX(*insptr++);

                int32_t sectnum = Gv_GetVarX(*insptr++);
                int32_t vx = Gv_GetVarX(*insptr++), vy = Gv_GetVarX(*insptr++), vz = Gv_GetVarX(*insptr++);
                int32_t hitsectvar = *insptr++, hitwallvar = *insptr++, hitspritevar = *insptr++;
                int32_t hitxvar = *insptr++, hityvar = *insptr++, hitzvar = *insptr++, cliptype = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", sectnum);
                    continue;
                }

                hitdata_t hit;
                hitscan((const vec3_t *)&vect, sectnum, vx, vy, vz, &hit, cliptype);

                Gv_SetVarX(hitsectvar, hit.sect);
                Gv_SetVarX(hitwallvar, hit.wall);
                Gv_SetVarX(hitspritevar, hit.sprite);
                Gv_SetVarX(hitxvar, hit.pos.x);
                Gv_SetVarX(hityvar, hit.pos.y);
                Gv_SetVarX(hitzvar, hit.pos.z);
                continue;
            }

        case CON_CANSEE:
            insptr++;
            {
                int32_t x1=Gv_GetVarX(*insptr++), y1=Gv_GetVarX(*insptr++), z1=Gv_GetVarX(*insptr++);
                int32_t sect1=Gv_GetVarX(*insptr++);
                int32_t x2=Gv_GetVarX(*insptr++), y2=Gv_GetVarX(*insptr++), z2=Gv_GetVarX(*insptr++);
                int32_t sect2=Gv_GetVarX(*insptr++), rvar=*insptr++;

                if (EDUKE32_PREDICT_FALSE((unsigned)sect1 >= (unsigned)numsectors || (unsigned)sect2 >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector\n");
                    Gv_SetVarX(rvar, 0);
                }

                Gv_SetVarX(rvar, cansee(x1,y1,z1,sect1,x2,y2,z2,sect2));
                continue;
            }

        case CON_ROTATEPOINT:
            insptr++;
            {
                int32_t xpivot=Gv_GetVarX(*insptr++), ypivot=Gv_GetVarX(*insptr++);
                int32_t x=Gv_GetVarX(*insptr++), y=Gv_GetVarX(*insptr++), daang=Gv_GetVarX(*insptr++);
                int32_t x2var=*insptr++, y2var=*insptr++;
                int32_t x2, y2;

                rotatepoint(xpivot,ypivot,x,y,daang,&x2,&y2);
                Gv_SetVarX(x2var, x2);
                Gv_SetVarX(y2var, y2);
                continue;
            }

        case CON_NEARTAG:
            insptr++;
            {
                //             neartag(int32_t x, int32_t y, int32_t z, short sectnum, short ang,  //Starting position & angle
                //                     short *neartagsector,   //Returns near sector if sector[].tag != 0
                //                     short *neartagwall,     //Returns near wall if wall[].tag != 0
                //                     short *neartagsprite,   //Returns near sprite if sprite[].tag != 0
                //                     int32_t *neartaghitdist,   //Returns actual distance to object (scale: 1024=largest grid size)
                //                     int32_t neartagrange,      //Choose maximum distance to scan (scale: 1024=largest grid size)
                //                     char tagsearch)         //1-lotag only, 2-hitag only, 3-lotag&hitag

                int32_t x=Gv_GetVarX(*insptr++), y=Gv_GetVarX(*insptr++), z=Gv_GetVarX(*insptr++);
                int32_t sectnum=Gv_GetVarX(*insptr++), ang=Gv_GetVarX(*insptr++);
                int32_t neartagsectorvar=*insptr++, neartagwallvar=*insptr++, neartagspritevar=*insptr++, neartaghitdistvar=*insptr++;
                int32_t neartagrange=Gv_GetVarX(*insptr++), tagsearch=Gv_GetVarX(*insptr++);

                int16_t neartagsector, neartagwall, neartagsprite;
                int32_t neartaghitdist;

                if (EDUKE32_PREDICT_FALSE((unsigned)sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", sectnum);
                    continue;
                }
                neartag(x, y, z, sectnum, ang, &neartagsector, &neartagwall, &neartagsprite,
                        &neartaghitdist, neartagrange, tagsearch, NULL);

                Gv_SetVarX(neartagsectorvar, neartagsector);
                Gv_SetVarX(neartagwallvar, neartagwall);
                Gv_SetVarX(neartagspritevar, neartagsprite);
                Gv_SetVarX(neartaghitdistvar, neartaghitdist);
                continue;
            }

        case CON_GETTIMEDATE:
            insptr++;
            {
                int32_t i, vals[8];

                G_GetTimeDate(vals);

                for (i=0; i<8; i++)
                    Gv_SetVarX(*insptr++, vals[i]);

                continue;
            }

        case CON_MOVESPRITE:
        case CON_SETSPRITE:
            insptr++;
            {
                int32_t spritenum = Gv_GetVarX(*insptr++);
                vec3_t davector;

                davector.x = Gv_GetVarX(*insptr++);
                davector.y = Gv_GetVarX(*insptr++);
                davector.z = Gv_GetVarX(*insptr++);

                if (tw == CON_SETSPRITE)
                {
                    if (EDUKE32_PREDICT_FALSE((unsigned)spritenum >= MAXSPRITES))
                    {
                        CON_ERRPRINTF("invalid sprite ID %d\n", spritenum);
                        continue;
                    }
                    setsprite(spritenum, &davector);
                    continue;
                }

                {
                    int32_t cliptype = Gv_GetVarX(*insptr++);

                    if (EDUKE32_PREDICT_FALSE((unsigned)spritenum >= MAXSPRITES))
                    {
                        CON_ERRPRINTF("invalid sprite ID %d\n", spritenum);
                        insptr++;
                        continue;
                    }
                    Gv_SetVarX(*insptr++, A_MoveSprite(spritenum, &davector, cliptype));
                    continue;
                }
            }

        case CON_GETFLORZOFSLOPE:
        case CON_GETCEILZOFSLOPE:
            insptr++;
            {
                int32_t sectnum = Gv_GetVarX(*insptr++), x = Gv_GetVarX(*insptr++), y = Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE((unsigned)sectnum >= (unsigned)numsectors))
                {
                    CON_ERRPRINTF("Invalid sector %d\n", sectnum);
                    insptr++;
                    continue;
                }

                if (tw == CON_GETFLORZOFSLOPE)
                {
                    Gv_SetVarX(*insptr++, getflorzofslope(sectnum,x,y));
                    continue;
                }
                Gv_SetVarX(*insptr++, getceilzofslope(sectnum,x,y));
                continue;
            }

        case CON_UPDATESECTOR:
        case CON_UPDATESECTORZ:
            insptr++;
            {
                int32_t x=Gv_GetVarX(*insptr++), y=Gv_GetVarX(*insptr++);
                int32_t z=(tw==CON_UPDATESECTORZ)?Gv_GetVarX(*insptr++):0;
                int32_t var=*insptr++;
                int16_t w=sprite[vm.g_i].sectnum;

                if (tw==CON_UPDATESECTOR) updatesector(x,y,&w);
                else updatesectorz(x,y,z,&w);

                Gv_SetVarX(var, w);
                continue;
            }

        case CON_SPAWN:
            insptr++;
            if ((unsigned)vm.g_sp->sectnum >= MAXSECTORS)
            {
                CON_ERRPRINTF("Invalid sector %d\n", TrackerCast(vm.g_sp->sectnum));
                insptr++;
                continue;
            }
            A_Spawn(vm.g_i,*insptr++);
            continue;

        case CON_IFWASWEAPON:
            insptr++;
            VM_CONDITIONAL(actor[vm.g_i].picnum == *insptr);
            continue;

        case CON_IFAI:
            insptr++;
            VM_CONDITIONAL(AC_AI_ID(vm.g_t) == *insptr);
            continue;

        case CON_IFACTION:
            insptr++;
            VM_CONDITIONAL(AC_ACTION_ID(vm.g_t) == *insptr);
            continue;

        case CON_IFACTIONCOUNT:
            insptr++;
            VM_CONDITIONAL(AC_ACTION_COUNT(vm.g_t) >= *insptr);
            continue;

        case CON_RESETACTIONCOUNT:
            insptr++;
            AC_ACTION_COUNT(vm.g_t) = 0;
            continue;

        case CON_DEBRIS:
            insptr++;
            {
                int32_t dnum = *insptr++;
                int32_t s, l, j;

                if ((unsigned)vm.g_sp->sectnum < MAXSECTORS)
                    for (j=(*insptr)-1; j>=0; j--)
                    {
                        if (vm.g_sp->picnum == BLIMP && dnum == SCRAP1)
                            s = 0;
                        else s = (krand()%3);

                        l = A_InsertSprite(vm.g_sp->sectnum,
                                           vm.g_sp->x+(krand()&255)-128,vm.g_sp->y+(krand()&255)-128,vm.g_sp->z-(8<<8)-(krand()&8191),
                                           dnum+s,vm.g_sp->shade,32+(krand()&15),32+(krand()&15),
                                           krand()&2047,(krand()&127)+32,
                                           -(krand()&2047),vm.g_i,5);
                        if (vm.g_sp->picnum == BLIMP && dnum == SCRAP1)
                            sprite[l].yvel = BlimpSpawnSprites[j%14];
                        else sprite[l].yvel = -1;
                        sprite[l].pal = vm.g_sp->pal;
                    }
                insptr++;
            }
            continue;

        case CON_COUNT:
            insptr++;
            AC_COUNT(vm.g_t) = (int16_t) *insptr++;
            continue;

        case CON_CSTATOR:
            insptr++;
            vm.g_sp->cstat |= (int16_t) *insptr++;
            continue;

        case CON_CLIPDIST:
            insptr++;
            vm.g_sp->clipdist = (int16_t) *insptr++;
            continue;

        case CON_CSTAT:
            insptr++;
            vm.g_sp->cstat = (int16_t) *insptr++;
            continue;

        case CON_SAVENN:
        case CON_SAVE:
            insptr++;
            {
                g_lastSaveSlot = *insptr++;

                if ((unsigned)g_lastSaveSlot >= MAXSAVEGAMES)
                    continue;

                if (tw == CON_SAVE || ud.savegame[g_lastSaveSlot][0] == 0)
                {
                    time_t curtime = time(NULL);
                    struct tm *timeptr = localtime(&curtime);
                    Bsnprintf(ud.savegame[g_lastSaveSlot], sizeof(ud.savegame[g_lastSaveSlot]), "Auto %.4d%.2d%.2d %.2d%.2d%.2d\n",
                    timeptr->tm_year + 1900, timeptr->tm_mon, timeptr->tm_mday,
                    timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
                }

                OSD_Printf("Saving to slot %d\n",g_lastSaveSlot);

                KB_FlushKeyboardQueue();

                g_screenCapture = 1;
                G_DrawRooms(myconnectindex,65536);
                g_screenCapture = 0;

                G_SavePlayerMaybeMulti(g_lastSaveSlot);

                continue;
            }

        case CON_QUAKE:
            insptr++;
            g_earthquakeTime = Gv_GetVarX(*insptr++);
            A_PlaySound(EARTHQUAKE,g_player[screenpeek].ps->i);
            continue;

        case CON_IFMOVE:
            insptr++;
            VM_CONDITIONAL(AC_MOVE_ID(vm.g_t) == *insptr);
            continue;

        case CON_RESETPLAYER:
        {
            insptr++;
            vm.g_flags = VM_ResetPlayer(vm.g_p, vm.g_flags);
        }
        continue;

        case CON_IFONWATER:
            VM_CONDITIONAL(sector[vm.g_sp->sectnum].lotag == ST_1_ABOVE_WATER && klabs(vm.g_sp->z-sector[vm.g_sp->sectnum].floorz) < (32<<8));
            continue;

        case CON_IFINWATER:
            VM_CONDITIONAL(sector[vm.g_sp->sectnum].lotag == ST_2_UNDERWATER);
            continue;

        case CON_IFCOUNT:
            insptr++;
            VM_CONDITIONAL(AC_COUNT(vm.g_t) >= *insptr);
            continue;

        case CON_IFACTOR:
            insptr++;
            VM_CONDITIONAL(vm.g_sp->picnum == *insptr);
            continue;

        case CON_RESETCOUNT:
            insptr++;
            AC_COUNT(vm.g_t) = 0;
            continue;

        case CON_ADDINVENTORY:
        {
            insptr += 2;
            switch (*(insptr-1))
            {
            case GET_STEROIDS:
                ps->inv_amount[GET_STEROIDS] = *insptr;
                ps->inven_icon = ICON_STEROIDS;
                break;

            case GET_SHIELD:
                ps->inv_amount[GET_SHIELD] += *insptr;// 100;
                if (ps->inv_amount[GET_SHIELD] > ps->max_shield_amount)
                    ps->inv_amount[GET_SHIELD] = ps->max_shield_amount;
                break;

            case GET_SCUBA:
                ps->inv_amount[GET_SCUBA] = *insptr;// 1600;
                ps->inven_icon = ICON_SCUBA;
                break;

            case GET_HOLODUKE:
                ps->inv_amount[GET_HOLODUKE] = *insptr;// 1600;
                ps->inven_icon = ICON_HOLODUKE;
                break;

            case GET_JETPACK:
                ps->inv_amount[GET_JETPACK] = *insptr;// 1600;
                ps->inven_icon = ICON_JETPACK;
                break;

            case GET_ACCESS:
                switch (vm.g_sp->pal)
                {
                case  0:
                    ps->got_access |= 1;
                    break;
                case 21:
                    ps->got_access |= 2;
                    break;
                case 23:
                    ps->got_access |= 4;
                    break;
                }
                break;

            case GET_HEATS:
                ps->inv_amount[GET_HEATS] = *insptr;
                ps->inven_icon = ICON_HEATS;
                break;

            case GET_FIRSTAID:
                ps->inven_icon = ICON_FIRSTAID;
                ps->inv_amount[GET_FIRSTAID] = *insptr;
                break;

            case GET_BOOTS:
                ps->inven_icon = ICON_BOOTS;
                ps->inv_amount[GET_BOOTS] = *insptr;
                break;
            default:
                CON_ERRPRINTF("Invalid inventory ID %d\n", (int32_t)*(insptr-1));
                break;
            }
            insptr++;
            continue;
        }

        case CON_HITRADIUSVAR:
            insptr++;
            {
                int32_t v1=Gv_GetVarX(*insptr++),v2=Gv_GetVarX(*insptr++),v3=Gv_GetVarX(*insptr++);
                int32_t v4=Gv_GetVarX(*insptr++),v5=Gv_GetVarX(*insptr++);
                A_RadiusDamage(vm.g_i,v1,v2,v3,v4,v5);
            }
            continue;

        case CON_HITRADIUS:
            A_RadiusDamage(vm.g_i,*(insptr+1),*(insptr+2),*(insptr+3),*(insptr+4),*(insptr+5));
            insptr += 6;
            continue;

        case CON_IFP:
        {
            int32_t l = *(++insptr);
            int32_t j = 0;
            int32_t s = sprite[ps->i].xvel;

            if ((l&8) && ps->on_ground && TEST_SYNC_KEY(g_player[vm.g_p].sync->bits, SK_CROUCH))
                j = 1;
            else if ((l&16) && ps->jumping_counter == 0 && !ps->on_ground &&
                     ps->vel.z > 2048)
                j = 1;
            else if ((l&32) && ps->jumping_counter > 348)
                j = 1;
            else if ((l&1) && s >= 0 && s < 8)
                j = 1;
            else if ((l&2) && s >= 8 && !TEST_SYNC_KEY(g_player[vm.g_p].sync->bits, SK_RUN))
                j = 1;
            else if ((l&4) && s >= 8 && TEST_SYNC_KEY(g_player[vm.g_p].sync->bits, SK_RUN))
                j = 1;
            else if ((l&64) && ps->pos.z < (vm.g_sp->z-(48<<8)))
                j = 1;
            else if ((l&128) && s <= -8 && !TEST_SYNC_KEY(g_player[vm.g_p].sync->bits, SK_RUN))
                j = 1;
            else if ((l&256) && s <= -8 && TEST_SYNC_KEY(g_player[vm.g_p].sync->bits, SK_RUN))
                j = 1;
            else if ((l&512) && (ps->quick_kick > 0 || (PWEAPON(vm.g_p, ps->curr_weapon, WorksLike) == KNEE_WEAPON && ps->kickback_pic > 0)))
                j = 1;
            else if ((l&1024) && sprite[ps->i].xrepeat < 32)
                j = 1;
            else if ((l&2048) && ps->jetpack_on)
                j = 1;
            else if ((l&4096) && ps->inv_amount[GET_STEROIDS] > 0 && ps->inv_amount[GET_STEROIDS] < 400)
                j = 1;
            else if ((l&8192) && ps->on_ground)
                j = 1;
            else if ((l&16384) && sprite[ps->i].xrepeat > 32 && sprite[ps->i].extra > 0 && ps->timebeforeexit == 0)
                j = 1;
            else if ((l&32768) && sprite[ps->i].extra <= 0)
                j = 1;
            else if ((l&65536L))
            {
                if (vm.g_sp->picnum == APLAYER && (g_netServer || ud.multimode > 1))
                    j = G_GetAngleDelta(g_player[otherp].ps->ang,getangle(ps->pos.x-g_player[otherp].ps->pos.x,ps->pos.y-g_player[otherp].ps->pos.y));
                else
                    j = G_GetAngleDelta(ps->ang,getangle(vm.g_sp->x-ps->pos.x,vm.g_sp->y-ps->pos.y));

                if (j > -128 && j < 128)
                    j = 1;
                else
                    j = 0;
            }
            VM_CONDITIONAL((intptr_t) j);
        }
        continue;

        case CON_IFSTRENGTH:
            insptr++;
            VM_CONDITIONAL(vm.g_sp->extra <= *insptr);
            continue;

        case CON_GUTS:
            A_DoGuts(vm.g_i,*(insptr+1),*(insptr+2));
            insptr += 3;
            continue;

        case CON_IFSPAWNEDBY:
            insptr++;
            VM_CONDITIONAL(actor[vm.g_i].picnum == *insptr);
            continue;

        case CON_WACKPLAYER:
            insptr++;
            P_ForceAngle(ps);
            continue;

        case CON_FLASH:
            insptr++;
            sprite[vm.g_i].shade = -127;
            ps->visibility = -127;
            continue;

        case CON_SAVEMAPSTATE:
            G_SaveMapState();
            insptr++;
            continue;

        case CON_LOADMAPSTATE:
            G_RestoreMapState();
            insptr++;
            continue;

        case CON_CLEARMAPSTATE:
            insptr++;
            {
                int32_t j = Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE((unsigned)j >= MAXVOLUMES*MAXLEVELS))
                {
                    CON_ERRPRINTF("Invalid map number: %d\n", j);
                    continue;
                }

                G_FreeMapState(j);
            }
            continue;

        case CON_STOPALLSOUNDS:
            insptr++;
            if (screenpeek == vm.g_p)
                FX_StopAllSounds();
            continue;

        case CON_IFGAPZL:
            insptr++;
            VM_CONDITIONAL(((actor[vm.g_i].floorz - actor[vm.g_i].ceilingz) >> 8) < *insptr);
            continue;

        case CON_IFHITSPACE:
            VM_CONDITIONAL(TEST_SYNC_KEY(g_player[vm.g_p].sync->bits, SK_OPEN));
            continue;

        case CON_IFOUTSIDE:
            VM_CONDITIONAL(sector[vm.g_sp->sectnum].ceilingstat&1);
            continue;

        case CON_IFMULTIPLAYER:
            VM_CONDITIONAL((g_netServer || g_netClient || ud.multimode > 1));
            continue;

        case CON_IFCLIENT:
            VM_CONDITIONAL(g_netClient != NULL);
            continue;

        case CON_IFSERVER:
            VM_CONDITIONAL(g_netServer != NULL);
            continue;

        case CON_OPERATE:
            insptr++;
            if (sector[vm.g_sp->sectnum].lotag == 0)
            {
                int16_t neartagsector, neartagwall, neartagsprite;
                int32_t neartaghitdist;

                neartag(vm.g_sp->x,vm.g_sp->y,vm.g_sp->z-(32<<8),vm.g_sp->sectnum,vm.g_sp->ang,
                        &neartagsector,&neartagwall,&neartagsprite,&neartaghitdist, 768, 4+1, NULL);

                if (neartagsector >= 0 && isanearoperator(sector[neartagsector].lotag))
                    if ((sector[neartagsector].lotag&0xff) == ST_23_SWINGING_DOOR || sector[neartagsector].floorz == sector[neartagsector].ceilingz)
                        if ((sector[neartagsector].lotag&(16384|32768)) == 0)
                        {
                            int32_t j;

                            for (SPRITES_OF_SECT(neartagsector, j))
                                if (sprite[j].picnum == ACTIVATOR)
                                    break;

                            if (j == -1)
                                G_OperateSectors(neartagsector,vm.g_i);
                        }
            }
            continue;

        case CON_IFINSPACE:
            VM_CONDITIONAL(G_CheckForSpaceCeiling(vm.g_sp->sectnum));
            continue;

        case CON_SPRITEPAL:
            insptr++;
            if (vm.g_sp->picnum != APLAYER)
                actor[vm.g_i].tempang = vm.g_sp->pal;
            vm.g_sp->pal = *insptr++;
            continue;

        case CON_CACTOR:
            insptr++;
            vm.g_sp->picnum = *insptr++;
            continue;

        case CON_IFBULLETNEAR:
            VM_CONDITIONAL(A_Dodge(vm.g_sp) == 1);
            continue;

        case CON_IFRESPAWN:
            if (A_CheckEnemySprite(vm.g_sp)) VM_CONDITIONAL(ud.respawn_monsters)
            else if (A_CheckInventorySprite(vm.g_sp)) VM_CONDITIONAL(ud.respawn_inventory)
            else VM_CONDITIONAL(ud.respawn_items)
            continue;

        case CON_IFFLOORDISTL:
            insptr++;
            VM_CONDITIONAL((actor[vm.g_i].floorz - vm.g_sp->z) <= ((*insptr)<<8));
            continue;

        case CON_IFCEILINGDISTL:
            insptr++;
            VM_CONDITIONAL((vm.g_sp->z - actor[vm.g_i].ceilingz) <= ((*insptr)<<8));
            continue;

        case CON_PALFROM:
            insptr++;
            if (EDUKE32_PREDICT_FALSE((unsigned)vm.g_p >= (unsigned)playerswhenstarted))
            {
                CON_ERRPRINTF("invalid player ID %d\n", vm.g_p);
                insptr += 4;
            }
            else
            {
                uint8_t f=*insptr++, r=*insptr++, g=*insptr++, b=*insptr++;

                P_PalFrom(ps, f, r,g,b);
            }
            continue;

        case CON_SECTOROFWALL:
            insptr++;
            tw = *insptr++;
            Gv_SetVarX(tw, sectorofwall(Gv_GetVarX(*insptr++)));
            continue;

        case CON_QSPRINTF:
            insptr++;
            {
                int32_t dq = Gv_GetVarX(*insptr++), sq = Gv_GetVarX(*insptr++);
                if (EDUKE32_PREDICT_FALSE(ScriptQuotes[sq] == NULL || ScriptQuotes[dq] == NULL))
                {
                    CON_ERRPRINTF("null quote %d\n", ScriptQuotes[sq] ? dq : sq);

                    while ((*insptr & VM_INSTMASK) != CON_NULLOP)
                        Gv_GetVarX(*insptr++);

                    insptr++; // skip the NOP
                    continue;
                }

                {
                    int32_t arg[32], i = 0, j = 0, k = 0, numargs;
                    int32_t len = Bstrlen(ScriptQuotes[sq]);
                    char tempbuf[MAXQUOTELEN];

                    while ((*insptr & VM_INSTMASK) != CON_NULLOP && i < 32)
                        arg[i++] = Gv_GetVarX(*insptr++);
                    numargs = i;

                    insptr++; // skip the NOP

                    i = 0;

                    do
                    {
                        while (k < len && j < MAXQUOTELEN && ScriptQuotes[sq][k] != '%')
                            tempbuf[j++] = ScriptQuotes[sq][k++];

                        if (ScriptQuotes[sq][k] == '%')
                        {
                            k++;
                            switch (ScriptQuotes[sq][k])
                            {
                            case 'l':
                                if (ScriptQuotes[sq][k+1] != 'd')
                                {
                                    // write the % and l
                                    tempbuf[j++] = ScriptQuotes[sq][k-1];
                                    tempbuf[j++] = ScriptQuotes[sq][k++];
                                    break;
                                }
                                k++;
                            case 'd':
                            {
                                char buf[16];
                                int32_t ii;

                                if (i >= numargs)
                                    goto finish_qsprintf;
                                Bsprintf(buf, "%d", arg[i++]);

                                ii = Bstrlen(buf);
                                Bmemcpy(&tempbuf[j], buf, ii);
                                j += ii;
                                k++;
                            }
                            break;

                            case 's':
                            {
                                int32_t ii;

                                if (i >= numargs)
                                    goto finish_qsprintf;
                                ii = Bstrlen(ScriptQuotes[arg[i]]);

                                Bmemcpy(&tempbuf[j], ScriptQuotes[arg[i]], ii);
                                j += ii;
                                i++;
                                k++;
                            }
                            break;

                            default:
                                tempbuf[j++] = ScriptQuotes[sq][k-1];
                                break;
                            }
                        }
                    }
                    while (k < len && j < MAXQUOTELEN);
finish_qsprintf:
                    tempbuf[j] = '\0';
                    Bstrncpyz(ScriptQuotes[dq], tempbuf, MAXQUOTELEN);
                    continue;
                }
            }

        case CON_ADDLOG:
        {
            insptr++;

            OSD_Printf(OSDTEXT_GREEN "CONLOG: L=%d\n",g_errorLineNum);
            continue;
        }

        case CON_ADDLOGVAR:
            insptr++;
            {
                int32_t m=1;
                char szBuf[256];
                int32_t lVarID = *insptr;

                if ((lVarID >= g_gameVarCount) || lVarID < 0)
                {
                    if (*insptr==MAXGAMEVARS) // addlogvar for a constant?  Har.
                        insptr++;
                    //                else if (*insptr > g_gameVarCount && (*insptr < (MAXGAMEVARS<<1)+MAXGAMEVARS+1+MAXGAMEARRAYS))
                    else if (*insptr&(MAXGAMEVARS<<2))
                    {
                        int32_t index;

                        lVarID ^= (MAXGAMEVARS<<2);

                        if (lVarID&(MAXGAMEVARS<<1))
                        {
                            m = -m;
                            lVarID ^= (MAXGAMEVARS<<1);
                        }

                        insptr++;

                        index=Gv_GetVarX(*insptr++);
                        if (EDUKE32_PREDICT_TRUE((unsigned)index < (unsigned)aGameArrays[lVarID].size))
                        {
                            OSD_Printf(OSDTEXT_GREEN "%s: L=%d %s[%d] =%d\n", keyw[g_tw], g_errorLineNum,
                                       aGameArrays[lVarID].szLabel, index,
                                       (int32_t)(m*aGameArrays[lVarID].plValues[index]));
                            continue;
                        }
                        else
                        {
                            CON_ERRPRINTF("invalid array index\n");
                            continue;
                        }
                    }
                    else if (*insptr&(MAXGAMEVARS<<3))
                    {
                        //                    FIXME FIXME FIXME
                        if ((lVarID & (MAXGAMEVARS-1)) == g_iActorVarID)
                        {
                            intptr_t const *oinsptr = insptr++;
                            int32_t index = Gv_GetVarX(*insptr++);
                            insptr = oinsptr;
                            if (EDUKE32_PREDICT_FALSE((unsigned)index >= MAXSPRITES-1))
                            {
                                CON_ERRPRINTF("invalid array index\n");
                                Gv_GetVarX(*insptr++);
                                continue;
                            }
                            OSD_Printf(OSDTEXT_GREEN "%s: L=%d %d %d\n",keyw[g_tw],g_errorLineNum,index,Gv_GetVar(*insptr++,index,vm.g_p));
                            continue;
                        }
                    }
                    else if (EDUKE32_PREDICT_TRUE(*insptr&(MAXGAMEVARS<<1)))
                    {
                        m = -m;
                        lVarID ^= (MAXGAMEVARS<<1);
                    }
                    else
                    {
                        // invalid varID
                        insptr++;
                        CON_ERRPRINTF("invalid variable\n");
                        continue;  // out of switch
                    }
                }
                Bsprintf(szBuf,"CONLOGVAR: L=%d %s ",g_errorLineNum, aGameVars[lVarID].szLabel);
                strcpy(g_szBuf,szBuf);

                if (aGameVars[lVarID].dwFlags & GAMEVAR_READONLY)
                {
                    Bsprintf(szBuf," (read-only)");
                    strcat(g_szBuf,szBuf);
                }
                if (aGameVars[lVarID].dwFlags & GAMEVAR_PERPLAYER)
                {
                    Bsprintf(szBuf," (Per Player. Player=%d)",vm.g_p);
                }
                else if (aGameVars[lVarID].dwFlags & GAMEVAR_PERACTOR)
                {
                    Bsprintf(szBuf," (Per Actor. Actor=%d)",vm.g_i);
                }
                else
                {
                    Bsprintf(szBuf," (Global)");
                }
                Bstrcat(g_szBuf,szBuf);
                Bsprintf(szBuf," =%d\n", Gv_GetVarX(lVarID)*m);
                Bstrcat(g_szBuf,szBuf);
                OSD_Printf(OSDTEXT_GREEN "%s",g_szBuf);
                insptr++;
                continue;
            }

        case CON_SETSECTOR:
        case CON_GETSECTOR:
            insptr++;
            {
                // syntax [gs]etsector[<var>].x <VAR>
                // <varid> <xxxid> <varid>
                int32_t lVar1=*insptr++, lLabelID=*insptr++, lVar2=*insptr++;

                VM_AccessSector(tw==CON_SETSECTOR, lVar1, lLabelID, lVar2);
                continue;
            }

        case CON_SQRT:
            insptr++;
            {
                // syntax sqrt <invar> <outvar>
                int32_t lInVarID=*insptr++, lOutVarID=*insptr++;

                Gv_SetVarX(lOutVarID, ksqrt((uint32_t)Gv_GetVarX(lInVarID)));
                continue;
            }

        case CON_FINDNEARACTOR:
        case CON_FINDNEARSPRITE:
        case CON_FINDNEARACTOR3D:
        case CON_FINDNEARSPRITE3D:
            insptr++;
            {
                // syntax findnearactorvar <type> <maxdist> <getvar>
                // gets the sprite ID of the nearest actor within max dist
                // that is of <type> into <getvar>
                // -1 for none found
                // <type> <maxdist> <varid>
                int32_t lType=*insptr++, lMaxDist=*insptr++, lVarID=*insptr++;
                int32_t lFound=-1, j, k = MAXSTATUS-1;

                if (tw == CON_FINDNEARACTOR || tw == CON_FINDNEARACTOR3D)
                    k = 1;

                if (tw==CON_FINDNEARSPRITE3D || tw==CON_FINDNEARACTOR3D)
                {
                    do
                    {
                        j=headspritestat[k];    // all sprites
                        while (j>=0)
                        {
                            if (sprite[j].picnum == lType && j != vm.g_i && dist(&sprite[vm.g_i], &sprite[j]) < lMaxDist)
                            {
                                lFound=j;
                                j = MAXSPRITES;
                                break;
                            }
                            j = nextspritestat[j];
                        }
                        if (j == MAXSPRITES || tw == CON_FINDNEARACTOR3D)
                            break;
                    }
                    while (k--);
                    Gv_SetVarX(lVarID, lFound);
                    continue;
                }

                do
                {
                    j=headspritestat[k];    // all sprites
                    while (j>=0)
                    {
                        if (sprite[j].picnum == lType && j != vm.g_i && ldist(&sprite[vm.g_i], &sprite[j]) < lMaxDist)
                        {
                            lFound=j;
                            j = MAXSPRITES;
                            break;
                        }
                        j = nextspritestat[j];
                    }

                    if (j == MAXSPRITES || tw == CON_FINDNEARACTOR)
                        break;
                }
                while (k--);
                Gv_SetVarX(lVarID, lFound);
                continue;
            }

        case CON_FINDNEARACTORVAR:
        case CON_FINDNEARSPRITEVAR:
        case CON_FINDNEARACTOR3DVAR:
        case CON_FINDNEARSPRITE3DVAR:
            insptr++;
            {
                // syntax findnearactorvar <type> <maxdistvar> <getvar>
                // gets the sprite ID of the nearest actor within max dist
                // that is of <type> into <getvar>
                // -1 for none found
                // <type> <maxdistvarid> <varid>
                int32_t lType=*insptr++, lMaxDist=Gv_GetVarX(*insptr++), lVarID=*insptr++;
                int32_t lFound=-1, j, k = 1;

                if (tw == CON_FINDNEARSPRITEVAR || tw == CON_FINDNEARSPRITE3DVAR)
                    k = MAXSTATUS-1;

                if (tw==CON_FINDNEARACTOR3DVAR || tw==CON_FINDNEARSPRITE3DVAR)
                {
                    do
                    {
                        j=headspritestat[k];    // all sprites

                        while (j >= 0)
                        {
                            if (sprite[j].picnum == lType && j != vm.g_i && dist(&sprite[vm.g_i], &sprite[j]) < lMaxDist)
                            {
                                lFound=j;
                                j = MAXSPRITES;
                                break;
                            }
                            j = nextspritestat[j];
                        }
                        if (j == MAXSPRITES || tw==CON_FINDNEARACTOR3DVAR)
                            break;
                    }
                    while (k--);
                    Gv_SetVarX(lVarID, lFound);
                    continue;
                }

                do
                {
                    j=headspritestat[k];    // all sprites

                    while (j >= 0)
                    {
                        if (sprite[j].picnum == lType && j != vm.g_i && ldist(&sprite[vm.g_i], &sprite[j]) < lMaxDist)
                        {
                            lFound=j;
                            j = MAXSPRITES;
                            break;
                        }
                        j = nextspritestat[j];
                    }

                    if (j == MAXSPRITES || tw==CON_FINDNEARACTORVAR)
                        break;
                }
                while (k--);
                Gv_SetVarX(lVarID, lFound);
                continue;
            }

        case CON_FINDNEARACTORZVAR:
        case CON_FINDNEARSPRITEZVAR:
            insptr++;
            {
                // syntax findnearactorvar <type> <maxdistvar> <getvar>
                // gets the sprite ID of the nearest actor within max dist
                // that is of <type> into <getvar>
                // -1 for none found
                // <type> <maxdistvarid> <varid>
                int32_t lType=*insptr++, lMaxDist=Gv_GetVarX(*insptr++);
                int32_t lMaxZDist=Gv_GetVarX(*insptr++);
                int32_t lVarID=*insptr++, lFound=-1, lTemp, lTemp2, j, k=MAXSTATUS-1;
                do
                {
                    j=headspritestat[tw==CON_FINDNEARACTORZVAR?1:k];    // all sprites
                    if (j == -1) continue;
                    do
                    {
                        if (sprite[j].picnum == lType && j != vm.g_i)
                        {
                            lTemp=ldist(&sprite[vm.g_i], &sprite[j]);
                            if (lTemp < lMaxDist)
                            {
                                lTemp2=klabs(sprite[vm.g_i].z-sprite[j].z);
                                if (lTemp2 < lMaxZDist)
                                {
                                    lFound=j;
                                    j = MAXSPRITES;
                                    break;
                                }
                            }
                        }
                        j = nextspritestat[j];
                    }
                    while (j>=0);
                    if (tw==CON_FINDNEARACTORZVAR || j == MAXSPRITES)
                        break;
                }
                while (k--);
                Gv_SetVarX(lVarID, lFound);

                continue;
            }

        case CON_FINDNEARACTORZ:
        case CON_FINDNEARSPRITEZ:
            insptr++;
            {
                // syntax findnearactorvar <type> <maxdist> <getvar>
                // gets the sprite ID of the nearest actor within max dist
                // that is of <type> into <getvar>
                // -1 for none found
                // <type> <maxdist> <varid>
                int32_t lType=*insptr++, lMaxDist=*insptr++, lMaxZDist=*insptr++, lVarID=*insptr++;
                int32_t lTemp, lTemp2, lFound=-1, j, k=MAXSTATUS-1;
                do
                {
                    j=headspritestat[tw==CON_FINDNEARACTORZ?1:k];    // all sprites
                    if (j == -1) continue;
                    do
                    {
                        if (sprite[j].picnum == lType && j != vm.g_i)
                        {
                            lTemp=ldist(&sprite[vm.g_i], &sprite[j]);
                            if (lTemp < lMaxDist)
                            {
                                lTemp2=klabs(sprite[vm.g_i].z-sprite[j].z);
                                if (lTemp2 < lMaxZDist)
                                {
                                    lFound=j;
                                    j = MAXSPRITES;
                                    break;
                                }
                            }
                        }
                        j = nextspritestat[j];
                    }
                    while (j>=0);

                    if (tw==CON_FINDNEARACTORZ || j == MAXSPRITES)
                        break;
                }
                while (k--);
                Gv_SetVarX(lVarID, lFound);
                continue;
            }

        case CON_FINDPLAYER:
            insptr++;
            aGameVars[g_iReturnVarID].val.lValue = A_FindPlayer(&sprite[vm.g_i], &tw);
            Gv_SetVarX(*insptr++, tw);
            continue;

        case CON_FINDOTHERPLAYER:
            insptr++;
            aGameVars[g_iReturnVarID].val.lValue = P_FindOtherPlayer(vm.g_p,&tw);
            Gv_SetVarX(*insptr++, tw);
            continue;

        case CON_SETPLAYER:
            insptr++;
            {
                tw=*insptr++;
                int32_t lLabelID=*insptr++;
                int32_t lParm2 = (PlayerLabels[lLabelID].flags & LABEL_HASPARM2) ? Gv_GetVarX(*insptr++) : 0;
                VM_SetPlayer(tw, lLabelID, *insptr++, lParm2);
                continue;
            }

        case CON_GETPLAYER:
            insptr++;
            {
                tw=*insptr++;
                int32_t lLabelID=*insptr++;
                int32_t lParm2 = (PlayerLabels[lLabelID].flags & LABEL_HASPARM2) ? Gv_GetVarX(*insptr++) : 0;
                VM_GetPlayer(tw, lLabelID, *insptr++, lParm2);
                continue;
            }

        case CON_GETINPUT:
            insptr++;
            {
                tw=*insptr++;
                int32_t lLabelID=*insptr++, lVar2=*insptr++;
                VM_AccessPlayerInput(0, tw, lLabelID, lVar2);
                continue;
            }

        case CON_SETINPUT:
            insptr++;
            {
                tw=*insptr++;
                int32_t lLabelID=*insptr++, lVar2=*insptr++;
                VM_AccessPlayerInput(1, tw, lLabelID, lVar2);
                continue;
            }

        case CON_GETUSERDEF:
            insptr++;
            {
                tw=*insptr++;
                int32_t lVar2=*insptr++;
                VM_AccessUserdef(0, tw, lVar2);
                continue;
            }

        case CON_SETUSERDEF:
            insptr++;
            {
                tw=*insptr++;
                int32_t lVar2=*insptr++;
                VM_AccessUserdef(1, tw, lVar2);
                continue;
            }

        case CON_GETPROJECTILE:
            insptr++;
            {
                tw = Gv_GetVarX(*insptr++);
                int32_t lLabelID = *insptr++, lVar2 = *insptr++;
                VM_AccessProjectile(0, tw, lLabelID, lVar2);
                continue;
            }

        case CON_SETPROJECTILE:
            insptr++;
            {
                tw = Gv_GetVarX(*insptr++);
                int32_t lLabelID = *insptr++, lVar2 = *insptr++;
                VM_AccessProjectile(1, tw, lLabelID, lVar2);
                continue;
            }

        case CON_SETWALL:
            insptr++;
            {
                tw=*insptr++;
                int32_t lLabelID=*insptr++, lVar2=*insptr++;
                VM_AccessWall(1, tw, lLabelID, lVar2);
                continue;
            }

        case CON_GETWALL:
            insptr++;
            {
                tw=*insptr++;
                int32_t lLabelID=*insptr++, lVar2=*insptr++;
                VM_AccessWall(0, tw, lLabelID, lVar2);
                continue;
            }

        case CON_SETACTORVAR:
        case CON_GETACTORVAR:
            insptr++;
            {
                // syntax [gs]etactorvar[<var>].<varx> <VAR>
                // gets the value of the per-actor variable varx into VAR
                // <var> <varx> <VAR>
                int32_t lSprite=Gv_GetVarX(*insptr++), lVar1=*insptr++;
                int32_t lVar2=*insptr++;

                if (EDUKE32_PREDICT_FALSE((unsigned)lSprite >= MAXSPRITES))
                {
                    CON_ERRPRINTF("invalid sprite ID %d\n", lSprite);
                    if (lVar1 == MAXGAMEVARS || lVar1 & ((MAXGAMEVARS<<2)|(MAXGAMEVARS<<3))) insptr++;
                    if (lVar2 == MAXGAMEVARS || lVar2 & ((MAXGAMEVARS<<2)|(MAXGAMEVARS<<3))) insptr++;
                    continue;
                }

                if (tw == CON_SETACTORVAR)
                {
                    Gv_SetVar(lVar1, Gv_GetVarX(lVar2), lSprite, vm.g_p);
                    continue;
                }
                Gv_SetVarX(lVar2, Gv_GetVar(lVar1, lSprite, vm.g_p));
                continue;
            }

        case CON_SETPLAYERVAR:
        case CON_GETPLAYERVAR:
            insptr++;
            {
                int32_t iPlayer = (*insptr != g_iThisActorID) ? Gv_GetVarX(*insptr) : vm.g_p;

                insptr++;

                int32_t lVar1 = *insptr++, lVar2 = *insptr++;

                if (EDUKE32_PREDICT_FALSE((unsigned)iPlayer >= (unsigned)playerswhenstarted))
                {
                    CON_ERRPRINTF("invalid player ID %d\n", iPlayer);

                    if (lVar1 == MAXGAMEVARS || lVar1 & ((MAXGAMEVARS << 2) | (MAXGAMEVARS << 3)))
                        insptr++;

                    if (lVar2 == MAXGAMEVARS || lVar2 & ((MAXGAMEVARS << 2) | (MAXGAMEVARS << 3)))
                        insptr++;

                    continue;
                }

                if (tw == CON_SETPLAYERVAR)
                    Gv_SetVar(lVar1, Gv_GetVarX(lVar2), vm.g_i, iPlayer);
                else
                    Gv_SetVarX(lVar2, Gv_GetVar(lVar1, vm.g_i, iPlayer));

                continue;
            }

        case CON_SETACTOR:
            insptr++;
            {
                // syntax [gs]etactor[<var>].x <VAR>
                // <varid> <xxxid> <varid>

                int32_t lVar1 = *insptr++, lLabelID = *insptr++;
                int32_t lParm2 = (ActorLabels[lLabelID].flags & LABEL_HASPARM2) ? Gv_GetVarX(*insptr++) : 0;

                VM_SetSprite(lVar1, lLabelID, *insptr++, lParm2);
                continue;
            }

        case CON_GETACTOR:
            insptr++;
            {
                // syntax [gs]etactor[<var>].x <VAR>
                // <varid> <xxxid> <varid>

                int32_t lVar1=*insptr++, lLabelID=*insptr++;
                int32_t lParm2 = (ActorLabels[lLabelID].flags & LABEL_HASPARM2) ? Gv_GetVarX(*insptr++) : 0;

                VM_GetSprite(lVar1, lLabelID, *insptr++, lParm2);
                continue;
            }

        case CON_SETTSPR:
        case CON_GETTSPR:
            insptr++;
            {
                // syntax [gs]etactor[<var>].x <VAR>
                // <varid> <xxxid> <varid>

                int32_t lVar1=*insptr++, lLabelID=*insptr++, lVar2=*insptr++;

                VM_AccessTsprite(tw==CON_SETTSPR, lVar1, lLabelID, lVar2);
                continue;
            }

        case CON_GETANGLETOTARGET:
            insptr++;
            // Actor[vm.g_i].lastvx and lastvy are last known location of target.
            Gv_SetVarX(*insptr++, getangle(actor[vm.g_i].lastvx-vm.g_sp->x,actor[vm.g_i].lastvy-vm.g_sp->y));
            continue;

        case CON_ANGOFFVAR:
            insptr++;
            spriteext[vm.g_i].angoff = Gv_GetVarX(*insptr++);
            continue;

        case CON_LOCKPLAYER:
            insptr++;
            ps->transporter_hold = Gv_GetVarX(*insptr++);
            continue;

        case CON_CHECKAVAILWEAPON:
            insptr++;
            tw = (*insptr != g_iThisActorID) ? Gv_GetVarX(*insptr) : vm.g_p;
            insptr++;

            if (EDUKE32_PREDICT_FALSE((unsigned)tw >= (unsigned)playerswhenstarted))
            {
                CON_ERRPRINTF("Invalid player ID %d\n", tw);
                continue;
            }

            P_CheckWeapon(g_player[tw].ps);
            continue;

        case CON_CHECKAVAILINVEN:
            insptr++;
            tw = (*insptr != g_iThisActorID) ? Gv_GetVarX(*insptr) : vm.g_p;
            insptr++;

            if (EDUKE32_PREDICT_FALSE((unsigned)tw >= (unsigned)playerswhenstarted))
            {
                CON_ERRPRINTF("Invalid player ID %d\n", tw);
                continue;
            }

            P_SelectNextInvItem(g_player[tw].ps);
            continue;

        case CON_GETPLAYERANGLE:
            insptr++;
            Gv_SetVarX(*insptr++, ps->ang);
            continue;

        case CON_GETACTORANGLE:
            insptr++;
            Gv_SetVarX(*insptr++, vm.g_sp->ang);
            continue;

        case CON_SETPLAYERANGLE:
            insptr++;
            ps->ang = Gv_GetVarX(*insptr++) & 2047;
            continue;

        case CON_SETACTORANGLE:
            insptr++;
            vm.g_sp->ang = Gv_GetVarX(*insptr++) & 2047;
            continue;

        case CON_SETVAR:
            insptr++;
            if ((aGameVars[*insptr].dwFlags & (GAMEVAR_USER_MASK | GAMEVAR_PTR_MASK)) == 0)
                aGameVars[*insptr].val.lValue = *(insptr + 1);
            else
                Gv_SetVarX(*insptr, *(insptr + 1));
            insptr += 2;
            continue;

        case CON_SETARRAY:
            insptr++;
            {
                tw=*insptr++;
                int32_t index = Gv_GetVarX(*insptr++);
                int32_t value = Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE((unsigned)tw >= (unsigned)g_gameArrayCount || (unsigned)index >= (unsigned)aGameArrays[tw].size))
                {
                    OSD_Printf(OSD_ERROR "Gv_SetVar(): tried to set invalid array ID (%d) or index out of bounds from sprite %d (%d), player %d\n",
                        tw,vm.g_i,TrackerCast(sprite[vm.g_i].picnum),vm.g_p);
                    continue;
                }
                if (EDUKE32_PREDICT_FALSE(aGameArrays[tw].dwFlags & GAMEARRAY_READONLY))
                {
                    OSD_Printf("Tried to set on read-only array `%s'", aGameArrays[tw].szLabel);
                    continue;
                }
                aGameArrays[tw].plValues[index]=value;
                continue;
            }
        case CON_WRITEARRAYTOFILE:
        case CON_READARRAYFROMFILE:
            insptr++;
            {
                const int32_t j=*insptr++;
                const int q = *insptr++;

                if (EDUKE32_PREDICT_FALSE(ScriptQuotes[q] == NULL))
                {
                    CON_ERRPRINTF("null quote %d\n", q);
                    continue;
                }

                if (tw == CON_READARRAYFROMFILE)
                {
                    int32_t fil = kopen4loadfrommod(ScriptQuotes[q], 0);

                    if (fil < 0)
                        continue;

                    int32_t numelts = kfilelength(fil) / sizeof(int32_t);

                    // NOTE: LunaCON is stricter: if the file has no
                    // elements, resize the array to size zero.
                    if (numelts > 0)
                    {
                        /*OSD_Printf(OSDTEXT_GREEN "CON_RESIZEARRAY: resizing array %s from %d to %d\n",
                            aGameArrays[j].szLabel, aGameArrays[j].size, numelts);*/

                        int32_t numbytes = numelts * sizeof(int32_t);
#ifdef BITNESS64
                        int32_t *tmpar = (int32_t *)Xmalloc(numbytes);
                        kread(fil, tmpar, numbytes);
#endif
                        Baligned_free(aGameArrays[j].plValues);
                        aGameArrays[j].plValues = (intptr_t *)Xaligned_alloc(ACTOR_VAR_ALIGNMENT, numelts * GAR_ELTSZ);
                        aGameArrays[j].size = numelts;
#ifdef BITNESS64
                        for (int32_t i = 0; i < numelts; i++)
                            aGameArrays[j].plValues[i] = tmpar[i];  // int32_t --> int64_t
                        Bfree(tmpar);
#else
                        kread(fil, aGameArrays[j].plValues, numbytes);
#endif
                    }

                    kclose(fil);
                    continue;
                }

                char temp[BMAX_PATH];

                if (EDUKE32_PREDICT_FALSE(G_ModDirSnprintf(temp, sizeof(temp), "%s", ScriptQuotes[q])))
                {
                    CON_ERRPRINTF("file name too long\n");
                    continue;
                }

                FILE *const fil = fopen(temp, "wb");

                if (EDUKE32_PREDICT_FALSE(fil == NULL))
                {
                    CON_ERRPRINTF("couldn't open file \"%s\"\n", temp);
                    continue;
                }

                const int32_t n = aGameArrays[j].size;
#ifdef BITNESS64
                int32_t *const array = (int32_t *)Xmalloc(sizeof(int32_t) * n);
                for (int32_t k = 0; k < n; k++) array[k] = aGameArrays[j].plValues[k];
#else
                int32_t *const array = (int32_t *)aGameArrays[j].plValues;
#endif
                fwrite(array, 1, sizeof(int32_t) * n, fil);
#ifdef BITNESS64
                Bfree(array);
#endif
                fclose(fil);

                continue;
            }

        case CON_GETARRAYSIZE:
            insptr++;
            tw = *insptr++;
            Gv_SetVarX(*insptr++,(aGameArrays[tw].dwFlags & GAMEARRAY_VARSIZE) ?
                       Gv_GetVarX(aGameArrays[tw].size) : aGameArrays[tw].size);
            continue;

        case CON_RESIZEARRAY:
            insptr++;
            {
                tw=*insptr++;
                int32_t asize = Gv_GetVarX(*insptr++);

                if (asize > 0)
                {
                    /*OSD_Printf(OSDTEXT_GREEN "CON_RESIZEARRAY: resizing array %s from %d to %d\n", aGameArrays[j].szLabel, aGameArrays[j].size, asize);*/
                    Baligned_free(aGameArrays[tw].plValues);
                    aGameArrays[tw].plValues = (intptr_t *)Xaligned_alloc(ACTOR_VAR_ALIGNMENT, GAR_ELTSZ * asize);
                    aGameArrays[tw].size = asize;
                }
                continue;
            }

        case CON_COPY:
            insptr++;
            {
                int32_t si=*insptr++;
                int32_t sidx = Gv_GetVarX(*insptr++); //, vm.g_i, vm.g_p);
                int32_t di=*insptr++;
                int32_t didx = Gv_GetVarX(*insptr++);
                int32_t numelts = Gv_GetVarX(*insptr++);

                tw = 0;

                if (EDUKE32_PREDICT_FALSE((unsigned)si>=(unsigned)g_gameArrayCount))
                {
                    CON_ERRPRINTF("Invalid array %d!", si);
                    tw = 1;
                }
                if (EDUKE32_PREDICT_FALSE((unsigned)di>=(unsigned)g_gameArrayCount))
                {
                    CON_ERRPRINTF("Invalid array %d!", di);
                    tw = 1;
                }
                if (EDUKE32_PREDICT_FALSE(aGameArrays[di].dwFlags & GAMEARRAY_READONLY))
                {
                    CON_ERRPRINTF("Array %d is read-only!", di);
                    tw = 1;
                }

                if (EDUKE32_PREDICT_FALSE(tw)) continue; // dirty replacement for VMFLAG_ERROR

                int32_t ssiz = (aGameArrays[si].dwFlags&GAMEARRAY_VARSIZE) ?
                       Gv_GetVarX(aGameArrays[si].size) : aGameArrays[si].size;
                int32_t dsiz = (aGameArrays[di].dwFlags&GAMEARRAY_VARSIZE) ?
                       Gv_GetVarX(aGameArrays[si].size) : aGameArrays[di].size;

                if (EDUKE32_PREDICT_FALSE(sidx > ssiz || didx > dsiz)) continue;
                if ((sidx+numelts) > ssiz) numelts = ssiz-sidx;
                if ((didx+numelts) > dsiz) numelts = dsiz-didx;

                // Switch depending on the source array type.
                switch (aGameArrays[si].dwFlags & GAMEARRAY_TYPE_MASK)
                {
                case 0:
                    // CON array to CON array.
                    Bmemcpy(aGameArrays[di].plValues+didx, aGameArrays[si].plValues+sidx, numelts*GAR_ELTSZ);
                    break;
                case GAMEARRAY_OFINT:
                    // From int32-sized array. Note that the CON array element
                    // type is intptr_t, so it is different-sized on 64-bit
                    // archs, but same-sized on 32-bit ones.
                    for (; numelts>0; numelts--)
                        (aGameArrays[di].plValues)[didx++] = ((int32_t *)aGameArrays[si].plValues)[sidx++];
                    break;
                case GAMEARRAY_OFSHORT:
                    // From int16_t array. Always different-sized.
                    for (; numelts>0; numelts--)
                        (aGameArrays[di].plValues)[didx++] = ((int16_t *)aGameArrays[si].plValues)[sidx++];
                    break;
                case GAMEARRAY_OFCHAR:
                    // From char array. Always different-sized.
                    for (; numelts>0; numelts--)
                        (aGameArrays[di].plValues)[didx++] = ((uint8_t *)aGameArrays[si].plValues)[sidx++];
                    break;
                }
                continue;
            }

        case CON_RANDVAR:
            insptr++;
            Gv_SetVarX(*insptr, mulscale16(krand(), *(insptr + 1) + 1));
            insptr += 2;
            continue;

        case CON_DISPLAYRANDVAR:
            insptr++;
            Gv_SetVarX(*insptr, mulscale15(system_15bit_rand(), *(insptr + 1) + 1));
            insptr += 2;
            continue;

        case CON_INV:
            if ((aGameVars[*(insptr + 1)].dwFlags & (GAMEVAR_USER_MASK | GAMEVAR_PTR_MASK)) == 0)
                aGameVars[*(insptr + 1)].val.lValue = -aGameVars[*(insptr + 1)].val.lValue;
            else
                Gv_SetVarX(*(insptr + 1), -Gv_GetVarX(*(insptr + 1)));
            insptr += 2;
            continue;

        case CON_MULVAR:
            insptr++;
            Gv_MulVar(*insptr, *(insptr + 1));
            insptr += 2;
            continue;

        case CON_DIVVAR:
            insptr++;
            if (EDUKE32_PREDICT_FALSE(*(insptr + 1) == 0))
            {
                CON_ERRPRINTF("divide by zero!\n");
                insptr += 2;
                continue;
            }
            Gv_DivVar(*insptr, *(insptr + 1));
            insptr += 2;
            continue;

        case CON_MODVAR:
            insptr++;
            if (EDUKE32_PREDICT_FALSE(*(insptr + 1) == 0))
            {
                CON_ERRPRINTF("mod by zero!\n");
                insptr += 2;
                continue;
            }

            Gv_ModVar(*insptr, *(insptr + 1));
            insptr += 2;
            continue;

        case CON_ANDVAR:
            insptr++;
            Gv_AndVar(*insptr, *(insptr + 1));
            insptr += 2;
            continue;

        case CON_ORVAR:
            insptr++;
            Gv_OrVar(*insptr, *(insptr + 1));
            insptr += 2;
            continue;

        case CON_XORVAR:
            insptr++;
            Gv_XorVar(*insptr, *(insptr + 1));
            insptr += 2;
            continue;

        case CON_SETVARVAR:
            insptr++;
            {
                tw = *insptr++;
                int32_t const gv = Gv_GetVarX(*insptr++);

                if ((aGameVars[tw].dwFlags & (GAMEVAR_USER_MASK | GAMEVAR_PTR_MASK)) == 0)
                    aGameVars[tw].val.lValue = gv;
                else
                    Gv_SetVarX(tw, gv);
            }
            continue;

        case CON_RANDVARVAR:
            insptr++;
            tw = *insptr++;
            Gv_SetVarX(tw, mulscale16(krand(), Gv_GetVarX(*insptr++) + 1));
            continue;

        case CON_DISPLAYRANDVARVAR:
            insptr++;
            tw = *insptr++;
            Gv_SetVarX(tw, mulscale15(system_15bit_rand(), Gv_GetVarX(*insptr++) + 1));
            continue;

        case CON_GMAXAMMO:
            insptr++;
            tw = Gv_GetVarX(*insptr++);
            if (EDUKE32_PREDICT_FALSE((unsigned)tw >= MAX_WEAPONS))
            {
                CON_ERRPRINTF("Invalid weapon ID %d\n", tw);
                insptr++;
                continue;
            }
            Gv_SetVarX(*insptr++, ps->max_ammo_amount[tw]);
            continue;

        case CON_SMAXAMMO:
            insptr++;
            tw = Gv_GetVarX(*insptr++);
            if (EDUKE32_PREDICT_FALSE((unsigned)tw >= MAX_WEAPONS))
            {
                CON_ERRPRINTF("Invalid weapon ID %d\n", tw);
                insptr++;
                continue;
            }
            ps->max_ammo_amount[tw] = Gv_GetVarX(*insptr++);
            continue;

        case CON_MULVARVAR:
            insptr++;
            tw = *insptr++;
            Gv_MulVar(tw, Gv_GetVarX(*insptr++));
            continue;

        case CON_DIVVARVAR:
            insptr++;
            {
                tw=*insptr++;
                int32_t const l2=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE(!l2))
                {
                    CON_ERRPRINTF("divide by zero!\n");
                    continue;
                }

                Gv_DivVar(tw, l2);
                continue;
            }

        case CON_MODVARVAR:
            insptr++;
            {
                tw=*insptr++;
                int32_t const l2=Gv_GetVarX(*insptr++);

                if (EDUKE32_PREDICT_FALSE(!l2))
                {
                    CON_ERRPRINTF("mod by zero!\n");
                    continue;
                }


                Gv_ModVar(tw, l2);
                continue;
            }

        case CON_ANDVARVAR:
            insptr++;
            tw = *insptr++;
            Gv_AndVar(tw, Gv_GetVarX(*insptr++));
            continue;

        case CON_XORVARVAR:
            insptr++;
            tw = *insptr++;
            Gv_XorVar(tw, Gv_GetVarX(*insptr++));
            continue;

        case CON_ORVARVAR:
            insptr++;
            tw = *insptr++;
            Gv_OrVar(tw, Gv_GetVarX(*insptr++));
            continue;

        case CON_SUBVAR:
            insptr++;
            Gv_SubVar(*insptr, *(insptr+1));
            insptr += 2;
            continue;

        case CON_SUBVARVAR:
            insptr++;
            tw = *insptr++;
            Gv_SubVar(tw, Gv_GetVarX(*insptr++));
            continue;

        case CON_ADDVAR:
            insptr++;
            Gv_AddVar(*insptr, *(insptr+1));
            insptr += 2;
            continue;

        case CON_SHIFTVARL:
            insptr++;
            if ((aGameVars[*insptr].dwFlags & (GAMEVAR_USER_MASK|GAMEVAR_PTR_MASK)) == 0)
            {
                aGameVars[*insptr].val.lValue <<= *(insptr+1);
                insptr += 2;
                continue;
            }
            Gv_SetVarX(*insptr, Gv_GetVarX(*insptr) << *(insptr+1));
            insptr += 2;
            continue;

        case CON_SHIFTVARR:
            insptr++;
            if ((aGameVars[*insptr].dwFlags & (GAMEVAR_USER_MASK|GAMEVAR_PTR_MASK)) == 0)
            {
                aGameVars[*insptr].val.lValue >>= *(insptr+1);
                insptr += 2;
                continue;
            }
            Gv_SetVarX(*insptr, Gv_GetVarX(*insptr) >> *(insptr+1));
            insptr += 2;
            continue;

        case CON_SIN:
            insptr++;
            Gv_SetVarX(*insptr, sintable[Gv_GetVarX(*(insptr+1))&2047]);
            insptr += 2;
            continue;

        case CON_COS:
            insptr++;
            Gv_SetVarX(*insptr, sintable[(Gv_GetVarX(*(insptr+1))+512)&2047]);
            insptr += 2;
            continue;

        case CON_ADDVARVAR:
            insptr++;
            tw = *insptr++;
            Gv_AddVar(tw, Gv_GetVarX(*insptr++));
            continue;

        case CON_SPGETLOTAG:
            insptr++;
            aGameVars[g_iLoTagID].val.lValue = vm.g_sp->lotag;
            continue;

        case CON_SPGETHITAG:
            insptr++;
            aGameVars[g_iHiTagID].val.lValue = vm.g_sp->hitag;
            continue;

        case CON_SECTGETLOTAG:
            insptr++;
            aGameVars[g_iLoTagID].val.lValue = sector[vm.g_sp->sectnum].lotag;
            continue;

        case CON_SECTGETHITAG:
            insptr++;
            aGameVars[g_iHiTagID].val.lValue = sector[vm.g_sp->sectnum].hitag;
            continue;

        case CON_GETTEXTUREFLOOR:
            insptr++;
            aGameVars[g_iTextureID].val.lValue = sector[vm.g_sp->sectnum].floorpicnum;
            continue;

&n