Subversion Repositories eduke32

Rev

Rev 8478 | Rev 8490 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman
// Ken Silverman's official web site: "http://www.advsys.net/ken"
// See the included license file "BUILDLIC.TXT" for license info.
//
// This file has been modified from Ken Silverman's original release
// by Jonathon Fowler (jf@jonof.id.au)
// by the EDuke32 team (development@voidpoint.com)

#include "build.h"

#include "baselayer.h"
#include "cache1d.h"
#include "colmatch.h"
#include "common.h"
#include "compat.h"
#include "editor.h"
#include "m32script.h"
#include "osd.h"
#include "palette.h"
#include "pragmas.h"
#include "renderlayer.h"
#include "scancodes.h"
#include "vfs.h"

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

char levelname[BMAX_PATH] = {0};

#define updatecrc16(crc,dat) (crc = (((crc<<8)&65535)^crctable[((((uint16_t)crc)>>8)&65535)^dat]))
static int32_t crctable[256];
static char kensig[64];

static const char *CallExtGetVer(void);
static int32_t CallExtInit(void);
static int32_t CallExtPreInit(int32_t argc,char const * const * argv);
static int32_t CallExtPostStartupWindow(void);
static void CallExtPostInit(void);
static void CallExtUnInit(void);
static void CallExtPreCheckKeys(void);
static void CallExtAnalyzeSprites(int32_t, int32_t, int32_t, int32_t, int32_t);
static void CallExtCheckKeys(void);
static void CallExtPreLoadMap(void);
static void CallExtSetupMapFilename(const char *mapname);
static void CallExtLoadMap(const char *mapname);
static int32_t CallExtPreSaveMap(void);
static void CallExtSaveMap(const char *mapname);
static inline const char *CallExtGetSectorCaption(int16_t sectnum) { return ExtGetSectorCaption(sectnum); }
static inline const char *CallExtGetWallCaption(int16_t wallnum) { return ExtGetWallCaption(wallnum); }
static inline const char *CallExtGetSpriteCaption(int16_t spritenum) { return ExtGetSpriteCaption(spritenum); }
static void CallExtShowSectorData(int16_t sectnum);
static void CallExtShowWallData(int16_t wallnum);
static void CallExtShowSpriteData(int16_t spritenum);
static void CallExtEditSectorData(int16_t sectnum);
static void CallExtEditWallData(int16_t wallnum);
static void CallExtEditSpriteData(int16_t spritenum);
// static const char *CallExtGetSectorType(int32_t lotag);

int8_t m32_clipping=2;
static int32_t m32_rotateang = 0;

// 0   1     2     3      4       5      6      7
// up, down, left, right, lshift, rctrl, lctrl, space
// 8  9  10    11    12   13
// a, z, pgdn, pgup, [,], [.]
// 14       15     16 17 18   19
// kpenter, enter, =, -, tab, `
uint8_t buildkeys[NUMBUILDKEYS] =
{
    0xc8,0xd0,0xcb,0xcd,0x2a,0x9d,0x1d,0x39,
    0x1e,0x2c,0xd1,0xc9,0x33,0x34,
    0x9c,0x1c,0xd,0xc,0xf,0x29
};

// Start position
vec3_t startpos;
int16_t startang, startsectnum;

// Current position
vec3_t pos;
int32_t horiz = 100;
int16_t ang, cursectnum;
static int32_t hvel, vel, svel, angvel;
int32_t g_doHardcodedMovement = 1;

static int32_t mousexsurp = 0, mouseysurp = 0;
double msens = 1.0;

int32_t grponlymode = 0;
int32_t graphicsmode = 0;

int32_t synctics = 0, lockclock = 0;

// those ones save the respective 3d video vars while in 2d mode
// so that exiting from mapster32 in 2d mode saves the correct ones
float vid_gamma_3d=-1, vid_contrast_3d=-1, vid_brightness_3d=-1;

int32_t xdim2d = 640, ydim2d = 480, xdimgame = 640, ydimgame = 480, bppgame = 8;
int32_t forcesetup = 1;

#ifndef GEKKO
int32_t g_maxCacheSize = 128<<20;
#else
int32_t g_maxCacheSize = 8<<20;
#endif

static int16_t oldmousebstatus = 0;

char game_executable[BMAX_PATH] = {0};

int32_t zlock = 0x7fffffff, zmode = 0, kensplayerheight = 32;
int16_t defaultspritecstat = 0;

int16_t localartfreq[MAXTILES];
int16_t localartlookup[MAXTILES], localartlookupnum;

char tempbuf[4096];

char names[MAXTILES][25];
const char *g_namesFileName = "NAMES.H";

int16_t asksave = 0;
int32_t osearchx, osearchy;                               //old search input

int32_t grid = 0, autogrid = 1, gridlock = 1, showtags = 2;
int32_t zoom = 768, gettilezoom = 1, ztarget = 768;
int32_t lastpm16time = 0;

extern int32_t mapversion;

int16_t highlight[MAXWALLS+MAXSPRITES];
int16_t highlightsector[MAXSECTORS], highlightsectorcnt = -1;
extern char textfont[128][8];

int32_t tempsectornum = -1;  // for auto ceiling/floor alignment
int32_t temppicnum, tempcstat, templotag, temphitag, tempextra;
uint32_t temppal, tempvis, tempxrepeat, tempyrepeat, tempxpanning=0, tempypanning=0;
int32_t tempshade, tempxvel, tempyvel, tempzvel;
int32_t tempstatnum=0, tempblend=0;
char somethingintab = 255;

// Only valid when highlightsectorcnt>0 and no structural
// modifications (deleting/inserting sectors or points, setting new firstwall)
// have been made:
static int16_t onextwall[MAXWALLS];  // onextwall[i]>=0 implies wall[i].nextwall < 0
static void mkonwvalid(void) { chsecptr_onextwall = onextwall; }
static void mkonwinvalid(void) { chsecptr_onextwall = NULL; tempsectornum=-1; }
static void mkonwinvalid_keeptempsect(void) { chsecptr_onextwall = NULL; }
static int32_t onwisvalid(void) { return chsecptr_onextwall != NULL; }

int32_t mlook = 0, mskip=0;
int32_t revertCTRL=0,scrollamount=3;
int32_t unrealedlook=1, quickmapcycling=1; //PK

char program_origcwd[BMAX_PATH];
const char *mapster32_fullpath;
char *testplay_addparam = 0;

static char boardfilename[BMAX_PATH], selectedboardfilename[BMAX_PATH];
//extern char levelname[BMAX_PATH];  // in astub.c   XXX: clean up this mess!!!

void B_SetBoardFileName(const char *fn)
{
    Bstrncpyz(boardfilename, fn, BMAX_PATH);
}

static fnlist_t fnlist;
static BUILDVFS_FIND_REC *finddirshigh=NULL, *findfileshigh=NULL;
static int32_t currentlist=0;

//static int32_t repeatcountx, repeatcounty;

static int32_t fillist[640];
// used for fillsector, batch point insertion, backup_highlighted_map
static int32_t tempxyar[MAXWALLS][2];

static int32_t mousx, mousy;
int16_t prefixtiles[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
uint8_t hlsectorbitmap[(MAXSECTORS+7)>>3];  // show2dsector is already taken...
static int32_t minhlsectorfloorz, numhlsecwalls;
int32_t searchlock = 0;

// used for:
//  - hl_all_bunch_sectors_p
//  - AlignWalls
//  - trace_loop
static uint8_t visited[(MAXWALLS+7)>>3];

int32_t m32_2d3dmode = 0;
int32_t m32_2d3dsize = 4;
vec2_t m32_2d3d = { 0xffff, 4 };

int32_t m32_3dundo = 1;

typedef struct
{
    int16_t numsectors, numwalls, numsprites;
#ifdef YAX_ENABLE
    int16_t numyaxbunches;
    int16_t *bunchnum;  // [numsectors][2]
    int16_t *ynextwall;  // [numwalls][2]
#endif
    usectortype *sector;
    uwalltype *wall;
    uspritetype *sprite;
} mapinfofull_t;

int32_t g_doScreenShot;

#define eitherALT   (keystatus[sc_LeftAlt]|keystatus[sc_RightAlt])
#define eitherCTRL  (keystatus[sc_LeftControl]|keystatus[sc_RightControl])
#define eitherSHIFT (keystatus[sc_LeftShift]|keystatus[sc_RightShift])

#define DOWN_BK(BuildKey) (keystatus[buildkeys[BK_##BuildKey]])

int32_t pk_turnaccel=16;
int32_t pk_turndecel=12;
int32_t pk_uedaccel=3;

int8_t keeptexturestretch = 1;
int8_t sideview_reversehrot = 0;

int16_t pointhighlightdist = 256;
int16_t linehighlightdist = 1024;

char lastpm16buf[156];

//static int32_t checksectorpointer_warn = 0;
static int32_t saveboard_savedtags, saveboard_fixedsprites;
static int32_t saveboard_canceled;

static int32_t backup_highlighted_map(mapinfofull_t *mapinfo);
static int32_t restore_highlighted_map(mapinfofull_t *mapinfo, int32_t forreal);
static void SaveBoardAndPrintMessage(const char *fn);

static int32_t adjustmark(int32_t *xplc, int32_t *yplc, int16_t danumwalls);
static void locktogrid(int32_t *dax, int32_t *day);
static int32_t checkautoinsert(int32_t dax, int32_t day, int16_t danumwalls);
static void keytimerstuff(void);
static void flipwalls(int16_t numwalls, int16_t newnumwalls);
static int32_t insertpoint(int16_t linehighlight, int32_t dax, int32_t day, int32_t *mapwallnum);
static void deletepoint(int16_t point, int32_t runi);
static int32_t deletesector(int16_t sucksect);
static int16_t whitelinescan(int16_t sucksect, int16_t dalinehighlight);
static void printcoords16(int32_t posxe, int32_t posye, int16_t ange);
static void overheadeditor(void);
static int32_t getlinehighlight(int32_t xplc, int32_t yplc, int32_t line, int8_t ignore_pointhighlight);
static int32_t movewalls(int32_t start, int32_t offs);
static void loadnames(const char *namesfile);
static void getclosestpointonwall(int32_t x, int32_t y, int32_t dawall, int32_t *nx, int32_t *ny,
                                  int32_t maybe_screen_coord_p);
static void initcrc(void);

static int32_t menuselect(void);
static int32_t menuselect_auto(int, int); //PK

static int32_t insert_sprite_common(int32_t sectnum, int32_t dax, int32_t day);
static void correct_ornamented_sprite(int32_t i, int32_t hitw);

static int32_t getfilenames(const char *path, const char *kind);

// Get basename of BUILD file name (forward slashes as directory separators).
static const char *getbasefn(const char *fn)
{
    const char *slash = Bstrrchr(fn, '/');
    return slash ? slash+1 : fn;
}

void clearkeys(void)
{
    Bmemset(keystatus,0,sizeof(keystatus));
}

#ifdef USE_OPENGL
int osdcmd_restartvid(osdcmdptr_t UNUSED(parm))
{
    UNREFERENCED_CONST_PARAMETER(parm);

    if (!in3dmode()) return OSDCMD_OK;

    videoResetMode();
    if (videoSetGameMode(fullscreen,xres,yres,bpp,upscalefactor))
        OSD_Printf("restartvid: Reset failed...\n");

    return OSDCMD_OK;
}
#endif

static int osdcmd_vidmode(osdcmdptr_t parm)
{
    int32_t newx = xres, newy = yres, newbpp = bpp, newfullscreen = fullscreen;
#ifdef USE_OPENGL
    int32_t tmp;
#endif

    switch (parm->numparms)
    {
#ifdef USE_OPENGL
    case 1:     // bpp switch
        tmp = Batol(parm->parms[0]);
        if (!(tmp==8 || tmp==16 || tmp==32))
            return OSDCMD_SHOWHELP;
        newbpp = tmp;
        break;
    case 4:     // fs, res, bpp switch
        newfullscreen = (Batol(parm->parms[3]) != 0);
        fallthrough__;
    case 3:     // res & bpp switch
        tmp = Batol(parm->parms[2]);
        if (!(tmp==8 || tmp==16 || tmp==32))
            return OSDCMD_SHOWHELP;
        newbpp = tmp;
        fallthrough__;
#endif
    case 2: // res switch
        newx = Batol(parm->parms[0]);
        newy = Batol(parm->parms[1]);
        break;
    default:
        return OSDCMD_SHOWHELP;
    }

    if (!in3dmode())
    {
        videoSet2dMode(newx,newy);
        xdim2d = xdim;
        ydim2d = ydim;

        videoBeginDrawing();    //{{{
        CLEARLINES2D(0, ydim16, 0);
        videoEndDrawing();      //}}}

        ydim16 = ydim-STATUS2DSIZ2;

        return OSDCMD_OK;
    }

    if (videoSetGameMode(newfullscreen,newx,newy,newbpp,upscalefactor))
        OSD_Printf("vidmode: Mode change failed!\n");

    xdimgame = newx;
    ydimgame = newy;
    bppgame = newbpp;
    fullscreen = newfullscreen;

    return OSDCMD_OK;
}


#ifdef M32_SHOWDEBUG
char m32_debugstr[64][128];
int32_t m32_numdebuglines=0;

static void M32_drawdebug(void)
{
    int32_t i;
    int32_t x=4, y=8;

#if 0
    {
        static char tstr[128];
        Bsprintf(tstr, "search... stat=%d, sector=%d, wall=%d (%d), isbottom=%d, asksave=%d",
                 searchstat, searchsector, searchwall, searchbottomwall, searchisbottom, asksave);
        printext256(x,y,whitecol,0,tstr,xdimgame>640?0:1);
    }
#endif
    if (m32_numdebuglines>0)
    {
        videoBeginDrawing();
        for (i=0; i<m32_numdebuglines && y<ydim-8; i++, y+=8)
            printext256(x,y,whitecol,0,m32_debugstr[i],xdimgame>640?0:1);
        videoEndDrawing();
    }
    m32_numdebuglines=0;
}
#endif

#ifdef YAX_ENABLE
// Check whether bunchnum has exactly one corresponding floor and ceiling
// and return it in this case. If not 1-to-1, return -1.
int32_t yax_is121(int16_t bunchnum, int16_t getfloor)
{
    int32_t i;
    i = headsectbunch[0][bunchnum];
    if (i<0 || nextsectbunch[0][i]>=0)
        return -1;
    i = headsectbunch[1][bunchnum];
    if (i<0 || nextsectbunch[1][i]>=0)
        return -1;

    return headsectbunch[getfloor][bunchnum];
}

static int32_t yax_numsectsinbunch(int16_t bunchnum, int16_t cf)
{
    int32_t i, n=0;

    if (bunchnum<0 || bunchnum>=numyaxbunches)
        return -1;

    for (SECTORS_OF_BUNCH(bunchnum, cf, i))
        n++;

    return n;
}

static void yax_fixreverselinks(int16_t oldwall, int16_t newwall)
{
    int32_t cf, ynw;
    for (cf=0; cf<2; cf++)
    {
        ynw = yax_getnextwall(oldwall, cf);
        if (ynw >= 0)
            yax_setnextwall(ynw, !cf, newwall);
    }
}

static void yax_tweakwalls(int16_t start, int16_t offs)
{
    int32_t i, nw, cf;
    for (i=0; i<numwalls; i++)
        for (cf=0; cf<2; cf++)
        {
            nw = yax_getnextwall(i, cf);
            if (nw >= start)
                yax_setnextwall(i, cf, nw+offs);
        }
}

static void yax_resetbunchnums(void)
{
    int32_t i;

    for (i=0; i<MAXSECTORS; i++)
        yax_setbunches(i, -1, -1);
    yax_update(1);
    yax_updategrays(pos.z);
}

// Whether a wall is constrained by sector extensions.
// If false, it's a wall that you can freely move around,
// attach points to, etc...
static int32_t yax_islockedwall(int16_t line)
{
#ifdef NEW_MAP_FORMAT
    return (wall[line].upwall>=0 || wall[line].dnwall>=0);
#else
    return !!(wall[line].cstat&YAX_NEXTWALLBITS);
#endif
}

# define DEFAULT_YAX_HEIGHT (2048<<4)
#endif

static void reset_default_mapstate(void)
{
    pos.x = 32768;          //new board!
    pos.y = 32768;
    pos.z = 0;
    ang = 1536;
    cursectnum = -1;

    numsectors = 0;
    numwalls = 0;

    editorzrange[0] = INT32_MIN;
    editorzrange[1] = INT32_MAX;

    initspritelists();
    taglab_init();
    artClearMapArt();
#ifdef YAX_ENABLE
    yax_resetbunchnums();
#endif
    g_loadedMapVersion = -1;
}

static void m32_keypresscallback(int32_t code, int32_t downp)
{
    UNREFERENCED_PARAMETER(downp);

    g_iReturnVar = code;
    VM_OnEvent(EVENT_KEYPRESS, -1);
}

void M32_ResetFakeRORTiles(void)
{
#ifdef POLYMER
# ifdef YAX_ENABLE
        // END_TWEAK ceiling/floor fake 'TROR' pics, see BEGIN_TWEAK in engine.c
        if (videoGetRenderMode() == REND_POLYMER && showinvisibility)
        {
            int32_t i;

            for (i=0; i<numyaxbunches; i++)
            {
                yax_tweakpicnums(i, YAX_CEILING, 1);
                yax_tweakpicnums(i, YAX_FLOOR, 1);
            }
        }
# endif
#endif
}

void M32_DrawRoomsAndMasks(void)
{
    static int srchwall = -1;
    const int32_t tmpyx=yxaspect, tmpvr=viewingrange;

    if (r_usenewaspect)
    {
        newaspect_enable = 1;
        videoSetCorrectedAspect();
    }

    VM_OnEvent(EVENT_PREDRAW3DSCREEN, -1);

    yax_preparedrawrooms();
    drawrooms(pos.x,pos.y,pos.z,ang,horiz,cursectnum);
    yax_drawrooms(CallExtAnalyzeSprites, cursectnum, 0, 0);

    const int osearchwall=searchwall, osearchstat=searchstat;
    if (srchwall >= 0)
    {
        // a.m32 states 'tduprot' and 'tduplin' need searchstat to check for
        // whether we've hit a sprite, but these would be only set after the
        // drawmasks(). Hence this hackish workaround.
        searchstat = 3;
        searchwall = srchwall;
    }
    CallExtAnalyzeSprites(0,0,0,0,0);
    searchwall = osearchwall, searchstat=osearchstat;

    renderDrawMasks();
    srchwall = (searchstat == 3) ? searchwall : -1;
    M32_ResetFakeRORTiles();

#ifdef POLYMER
    if (videoGetRenderMode() == REND_POLYMER && searchit == 2)
    {
        polymer_editorpick();
        drawrooms(pos.x,pos.y,pos.z,ang,horiz,cursectnum);
        CallExtAnalyzeSprites(0,0,0,0,0);
        renderDrawMasks();
        M32_ResetFakeRORTiles();
    }
#endif

    VM_OnEvent(EVENT_DRAW3DSCREEN, -1);

    if (g_doScreenShot)
    {
        videoCaptureScreen("mcapxxxx.tga", 0);
        g_doScreenShot = 0;
    }

    if (r_usenewaspect)
    {
        newaspect_enable = 0;
        renderSetAspect(tmpvr, tmpyx);
    }
}

void M32_OnShowOSD(int shown)
{
    mouseLockToWindow((!shown) + 2);
}

static void M32_FatalEngineError(void)
{
#ifdef DEBUGGINGAIDS
    debug_break();
#endif
    Bsprintf(tempbuf, "There was a problem initializing the engine: %s\n", engineerrstr);
    ERRprintf("%s", tempbuf);
    fatal_exit(tempbuf);
}

static void InitCustomColors()
{
    /* blue */
    vgapal16[9*4+0] = 252;
    vgapal16[9*4+1] = 124;
    vgapal16[9*4+2] = 28;

    /* orange */
    vgapal16[31*4+0] = 80; // blue
    vgapal16[31*4+1] = 180; // green
    vgapal16[31*4+2] = 240; // red

    // UNUSED?
    vgapal16[39*4+0] = 144;
    vgapal16[39*4+1] = 212;
    vgapal16[39*4+2] = 252;


    /* light yellow */
    vgapal16[22*4+0] = 204;
    vgapal16[22*4+1] = 252;
    vgapal16[22*4+2] = 252;

    /* grey */
    vgapal16[23*4+0] = 180;
    vgapal16[23*4+1] = 180;
    vgapal16[23*4+2] = 180;

    /* blue */
    vgapal16[24*4+0] = 204;
    vgapal16[24*4+1] = 164;
    vgapal16[24*4+2] = 48;

    vgapal16[32*4+0] = 240;
    vgapal16[32*4+1] = 200;
    vgapal16[32*4+2] = 84;

    // grid color
    vgapal16[25*4+0] = 64;
    vgapal16[25*4+1] = 56;
    vgapal16[25*4+2] = 56;

    vgapal16[26*4+0] = 96;
    vgapal16[26*4+1] = 96;
    vgapal16[26*4+2] = 96;

    // UNUSED?
    vgapal16[33*4+0] = 0; //60; // blue
    vgapal16[33*4+1] = 0; //120; // green
    vgapal16[33*4+2] = 192; //180; // red

    // UNUSED?
    vgapal16[41*4+0] = 0; //96;
    vgapal16[41*4+1] = 0; //160;
    vgapal16[41*4+2] = 252; //192;

    for (int i = 0; i<256; i++)
    {
        if (editorcolors[i] == 0)
        {
            palette_t *edcol = (palette_t *)&vgapal16[4*i];
            editorcolors[i] = paletteGetClosestColorUpToIndex(edcol->b,edcol->g,edcol->r, 239);
        }
    }
}

int app_main(int argc, char const * const * argv)
{
#ifdef STARTUP_SETUP_WINDOW
    char cmdsetup = 0;
#endif
    char quitflag;
    int32_t i;

    pathsearchmode = 1;         // unrestrict findfrompath so that full access to the filesystem can be had

#ifdef USE_OPENGL
    OSD_RegisterFunction("restartvid","restartvid: reinitialize the video mode",osdcmd_restartvid);
    OSD_RegisterFunction("vidmode","vidmode <xdim> <ydim> <bpp> <fullscreen>: immediately change the video mode",osdcmd_vidmode);
    baselayer_osdcmd_vidmode_func = osdcmd_vidmode;
#else
    OSD_RegisterFunction("vidmode","vidmode <xdim> <ydim>: immediately change the video mode",osdcmd_vidmode);
#endif

    wm_setapptitle(AppProperName);

    editstatus = 1;

    if ((i = CallExtPreInit(argc,argv)) < 0) return -1;

#ifdef _WIN32
    win_priorityclass = 1;
#endif

    for (i=1; i<argc; i++)
    {
        if (argv[i][0] == '-')
        {
#ifdef STARTUP_SETUP_WINDOW
            if (!Bstrcmp(argv[i], "-setup")) cmdsetup = 1;
            else
#endif
            if (!Bstrcmp(argv[i], "-help") || !Bstrcmp(argv[i], "--help") || !Bstrcmp(argv[i], "-?"))
            {
#ifdef WM_MSGBOX_WINDOW
                wm_msgbox(AppProperName,
#else
                Bprintf(
#endif
                    "%s\n"
                    "Syntax: %s [options] mapname\n"
                    "Options:\n"
                    "\t-grp\tUse an extra GRP or ZIP file.\n"
                    "\t-g\tSame as above.\n"
#ifdef STARTUP_SETUP_WINDOW
                    "\t-setup\tDisplays the configuration dialogue box before entering the editor.\n"
#endif
                    , AppProperName, AppTechnicalName);
                return 0;
            }
            continue;
        }
    }

    if (boardfilename[0] == 0)
        Bstrcpy(boardfilename,"newboard.map");
    else if (Bstrchr(boardfilename,'.') == 0)
        Bstrcat(boardfilename, ".map");
    //Bcanonicalisefilename(boardfilename,0);

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

    if (!buildvfs_getcwd(program_origcwd,BMAX_PATH))
        program_origcwd[0] = '\0';

    Bstrncpy(game_executable, DefaultGameLocalExec, sizeof(game_executable));

    if (enginePreInit())
        M32_FatalEngineError();

    if ((i = CallExtInit()) < 0) return -1;

#ifdef STARTUP_SETUP_WINDOW
    if (i || forcesetup || cmdsetup)
    {
        if (quitevent || !startwin_run())
        {
            engineUnInit();
            exit(EXIT_SUCCESS);
        }
    }
#endif

    if (CallExtPostStartupWindow() < 0) return -1;

    loadnames(g_namesFileName);

    if (initinput()) return -1;

    mouseInit();

    timerInit(CLOCKTICKSPERSECOND);
    timerSetCallback(keytimerstuff);

    artLoadFiles("tiles000.art", g_maxCacheSize);

    Bstrcpy(kensig,"Uses BUILD technology by Ken Silverman");
    initcrc();

    const char *defsfile = G_DefFile();

    if (testkopen("editor.def", 0))
        G_AddDefModule("editor.def");

    if (!loaddefinitionsfile(defsfile))
        initprintf("Definitions file \"%s\" loaded.\n",defsfile);

    for (char * m : g_defModules)
        free(m);
    g_defModules.clear();

    if (enginePostInit())
        M32_FatalEngineError();

    InitCustomColors();

    CallExtPostInit();

#ifdef YAX_ENABLE
    // init dummy texture for YAX
    // must be after loadpics(), which inits BUILD's cache

    i = MAXTILES-1;
    if (tilesiz[i].x==0 && tilesiz[i].y==0)
    {
        static char R[8*16] = { //
            0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
            0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0,
            0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0,
            0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0,
            0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
            0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
            0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0,
        };

        char *newtile;
        int32_t sx=32, sy=32, col, j;

        walock[i] = CACHE1D_PERMANENT;
        picsiz[i] = 5 + (5<<4);
        tilesiz[i].x = sx; tilesiz[i].y = sy;
        g_cache.allocateBlock(&waloff[i], sx*sy, &walock[i]);
        newtile = (char *)waloff[i];

        col = paletteGetClosestColor(128, 128, 0);
        for (j=0; j<(signed)sizeof(R); j++)
            R[j] *= col;

        Bmemset(newtile, 0, sx*sy);
        for (j=0; j<8; j++)
            Bmemcpy(&newtile[32*j], &R[16*j], 16);
    }
#endif

#ifdef HAVE_CLIPSHAPE_FEATURE
    int k = engineLoadClipMaps();
    if (k>0)
        initprintf("There was an error loading the sprite clipping map (status %d).\n", k);

    for (char * f : g_clipMapFiles)
        free(f);
    g_clipMapFiles.clear();
#endif

    taglab_init();

    mkonwinvalid();

    // executed once per init
    OSD_Exec("m32_autoexec.cfg");

    if (LoadBoard(boardfilename, 1))
        reset_default_mapstate();

    totalclock = 0;

    updatesector(pos.x,pos.y,&cursectnum);

    keySetCallback(&m32_keypresscallback);
    M32_OnShowOSD(0);  // make sure the desktop's mouse cursor is hidden

    if (cursectnum == -1)
    {
        vid_gamma_3d = g_videoGamma;
        vid_brightness_3d = g_videoBrightness;
        vid_contrast_3d = g_videoContrast;

        g_videoGamma = g_videoContrast = 1.0;
        g_videoBrightness = 0.0;

        videoSetPalette(0,0,0);
        if (videoSetGameMode(fullscreen, xdim2d, ydim2d, 8, upscalefactor) < 0)
        {
            CallExtUnInit();
            engineUnInit();
            Bprintf("%d * %d not supported in this graphics mode\n",xdim2d,ydim2d);
            Bexit(EXIT_SUCCESS);
        }

        system_getcvars();

        overheadeditor();
        keystatus[buildkeys[BK_MODE2D_3D]] = 0;

        g_videoGamma = vid_gamma_3d;
        g_videoContrast = vid_contrast_3d;
        g_videoBrightness = vid_brightness_3d;

        vid_gamma_3d = vid_contrast_3d = vid_brightness_3d = -1;

        videoSetPalette(GAMMA_CALC,0,0);
    }
    else
    {
        if (videoSetGameMode(fullscreen, xdimgame, ydimgame, bppgame, upscalefactor) < 0)
        {
            CallExtUnInit();
            engineUnInit();
            Bprintf("%d * %d not supported in this graphics mode\n",xdim,ydim);
            Bexit(EXIT_SUCCESS);
        }

        system_getcvars();

        videoSetPalette(GAMMA_CALC,0,0);
    }

CANCEL:
    quitflag = 0;
    while (quitflag == 0)
    {
        if (handleevents())
        {
            if (quitevent)
            {
                keystatus[sc_Escape] = 1;
                quitevent = 0;
            }
        }

        OSD_DispatchQueued();

        videoNextPage();
        synctics = (int32_t) totalclock-lockclock;
        lockclock += synctics;

        CallExtPreCheckKeys();

        M32_DrawRoomsAndMasks();

        inputchecked = 1;

#ifdef M32_SHOWDEBUG
        if (searchstat>=0 && (searchwall<0 || searchsector<0))
        {
            if (m32_numdebuglines<64)
                Bsprintf(m32_debugstr[m32_numdebuglines++], "inconsistent search variables!");
            searchstat = -1;
        }

        M32_drawdebug();
#endif
        CallExtCheckKeys();


        if (keystatus[sc_Escape])
        {
            keystatus[sc_Escape] = 0;

            printext256(0,0,whitecol,0,"Are you sure you want to quit?",0);

            videoShowFrame(1);
            synctics = (int32_t) totalclock-lockclock;
            lockclock += synctics;

            while ((keystatus[sc_Escape]|keystatus[sc_Enter]|keystatus[sc_Space]|keystatus[sc_N]) == 0)
            {
                idle_waitevent();
                if (handleevents())
                {
                    if (quitevent)
                    {
                        quitflag = 1;
                        break;
                    }
                }

                if (keystatus[sc_Y]||keystatus[sc_Enter]) // Y or ENTER
                {
                    keystatus[sc_Y] = 0;
                    keystatus[sc_Enter] = 0;
                    quitflag = 1; break;
                }
            }
            while (keystatus[sc_Escape])
            {
                keystatus[sc_Escape] = 0;
                quitevent = 0;
                goto CANCEL;
            }
        }
    }

    if (asksave)
    {
        i = CheckMapCorruption(4, 0);

        printext256(0,8,whitecol,0,i<4?"Save changes?":"Map is heavily corrupt. Save changes?",0);
        videoShowFrame(1);

        while ((keystatus[sc_Escape]|keystatus[sc_Enter]|keystatus[sc_Space]|keystatus[sc_N]|keystatus[sc_C]) == 0)
        {
            idle_waitevent();
            if (handleevents()) { if (quitevent) break; } // like saying no

            if (keystatus[sc_Y] || keystatus[sc_Enter]) // Y or ENTER
            {
                keystatus[sc_Y] = keystatus[sc_Enter] = 0;

                SaveBoard(NULL, M32_SB_ASKOV);

                break;
            }
        }
        while (keystatus[sc_Escape]||keystatus[sc_C])
        {
            keystatus[sc_Escape] = keystatus[sc_C] = 0;
            quitevent = 0;
            goto CANCEL;
        }
    }


    CallExtUnInit();
//    clearfilenames();
    engineUnInit();

    return 0;
}

static int32_t mhk=0;
static void loadmhk(int32_t domessage)
{
    char levname[BMAX_PATH];

    if (!mhk)
        return;

    Bstrcpy(levname, boardfilename);
    append_ext_UNSAFE(levname, ".mhk");

    if (!engineLoadMHK(levname))
    {
        if (domessage)
            message("Loaded map hack file \"%s\"",levname);
        else
            initprintf("Loaded map hack file \"%s\"\n",levname);
    }
    else
    {
        mhk=2;
        if (domessage)
            message("No maphack found for map \"%s\"",boardfilename);
    }
}

// this is spriteon{ceiling,ground}z from astub.c packed into
// one convenient function
void spriteoncfz(int32_t i, int32_t *czptr, int32_t *fzptr)
{
    int32_t height, zofs;

    getzsofslope(sprite[i].sectnum, sprite[i].x,sprite[i].y, czptr, fzptr);
    if ((sprite[i].cstat&48)==32)
        return;

    zofs = spriteheightofs(i, &height, 0);

    *czptr += height - zofs;
    *fzptr -= zofs;
}

static void move_and_update(int32_t xvect, int32_t yvect, int32_t addshr)
{
    if (m32_clipping==0)
    {
        pos.x += xvect>>(14+addshr);
        pos.y += yvect>>(14+addshr);
        updatesector(pos.x,pos.y, &cursectnum);
    }
    else
    {
        clipmove(&pos,&cursectnum, xvect>>addshr,yvect>>addshr,
                 128,4<<8,4<<8, (m32_clipping==1) ? 0 : CLIPMASK0);
    }

    if (in3dmode())
    {
        silentmessage("x:%d y:%d z:%d ang:%d horiz:%d", pos.x, pos.y, pos.z, ang, horiz);
        getmessagetimeoff = (int32_t) totalclock+30;
    }
}

static void mainloop_move(void)
{
    int32_t xvect, yvect, doubvel;

    if (angvel != 0)  //ang += angvel * constant
    {
        if (eitherCTRL && m32_2d3dmode)
        {
            int x = m32_2d3d.x + (angvel / 32);
            int xx = m32_2d3d.x + XSIZE_2D3D + (angvel / 32);

            if (x > 4 && xx < xdim2d - 4)
            {
                silentmessage("2d3d x:%d y:%d", m32_2d3d.x, m32_2d3d.y);
                m32_2d3d.x += (angvel / 32);
            }
        }
        else
        {
            //ENGINE calculates angvel for you

            //Lt. shift makes turn velocity 50% faster
            doubvel = (synctics + DOWN_BK(RUN)*(synctics>>1));

            ang += ((angvel*doubvel)>>4);
            ang &= 2047;

            if (in3dmode())
            {
                silentmessage("x:%d y:%d z:%d ang:%d horiz:%d", pos.x, pos.y, pos.z, ang, horiz);
                getmessagetimeoff = (int32_t) totalclock+30;
            }
        }
    }
    if ((vel|svel) != 0)
    {
        if (eitherCTRL && m32_2d3dmode)
        {
            int y = m32_2d3d.y - (vel / 64);
            int yy = m32_2d3d.y + YSIZE_2D3D - (vel / 64);

            if (y > 4 && yy < ydim2d - STATUS2DSIZ2 - 4)
            {
                silentmessage("2d3d x:%d y:%d", m32_2d3d.x, m32_2d3d.y);
                m32_2d3d.y -= (vel / 64);
            }
        }
        else

        {
            //Lt. shift doubles forward velocity
            doubvel = (1+(DOWN_BK(RUN)))*synctics;

            xvect = 0;
            yvect = 0;

            if (vel != 0)
            {
                xvect += ((vel*doubvel)>>3)*(int32_t) sintable[(ang+2560)&2047];
                yvect += ((vel*doubvel)>>3)*(int32_t) sintable[(ang+2048)&2047];
            }
            if (svel != 0)
            {
                xvect += ((svel*doubvel)>>3)*(int32_t) sintable[(ang+2048)&2047];
                yvect += ((svel*doubvel)>>3)*(int32_t) sintable[(ang+1536)&2047];
            }

            move_and_update(xvect, yvect, 0);
        }
    }
}

static void handle_sprite_in_clipboard(int32_t i)
{
    if (somethingintab == 3)
    {
        int32_t j, k;

        sprite[i].picnum = temppicnum;
        if (tilesiz[temppicnum].x <= 0 || tilesiz[temppicnum].y <= 0)
        {
            j = 0;
            for (k=0; k<MAXTILES; k++)
                if (tilesiz[k].x > 0 && tilesiz[k].y > 0)
                {
                    j = k;
                    break;
                }
            sprite[i].picnum = j;
        }
        sprite[i].shade = tempshade;
        sprite[i].blend = tempblend;
        sprite[i].pal = temppal;
        sprite[i].xrepeat = max(tempxrepeat, 1u);
        sprite[i].yrepeat = max(tempyrepeat, 1u);
        sprite[i].cstat = tempcstat;
    }
}


void editinput(void)
{
    int32_t mousz, bstatus;
    int32_t i, tempint=0;
    int32_t goalz, xvect, yvect, hiz, loz, oposz;
    int32_t hihit, lohit, omlook=mlook;

// 3B  3C  3D  3E   3F  40  41  42   43  44  57  58          46
// F1  F2  F3  F4   F5  F6  F7  F8   F9 F10 F11 F12        SCROLL

    mousz = 0;
    mouseGetValues(&mousx,&mousy,&bstatus);
    mousx = (mousx<<16) + mousexsurp;
    mousy = (mousy<<16) + mouseysurp;

    if (unrealedlook && !mskip)
    {
        if (mlook==0 && (bstatus&(1|2|4))==2)
            mlook = 3;
        else if ((bstatus&(1|2|4))==1)
            mlook = 3;
    }

    {
        ldiv_t ld;
        if (mlook)
        {
            ld = ldiv(mousx, (int32_t)((1<<16)/(msens*0.5f))); mousx = ld.quot; mousexsurp = ld.rem;
            ld = ldiv(mousy, (int32_t)((1<<16)/(msens*0.25f))); mousy = ld.quot; mouseysurp = ld.rem;
        }
        else
        {
            ld = ldiv(mousx, (int32_t)((1<<16)/msens)); mousx = ld.quot; mousexsurp = ld.rem;
            ld = ldiv(mousy, (int32_t)((1<<16)/msens)); mousy = ld.quot; mouseysurp = ld.rem;
        }
    }

    if (mlook == 3)
        mlook = omlook;

    // UnrealEd:
    // rmb: mouselook
    // lbm: x:turn y:fwd/back local x
    // lmb&rmb: x:strafe y:up/dn (move in local yz plane)
    // mmb: fwd/back in viewing vector

    if (unrealedlook && !mskip)    //PK
    {
        if ((bstatus&(1|2|4))==1)
        {
            ang += mousx;
            xvect = -((mousy*(int32_t)sintable[(ang+2560)&2047])<<(3+pk_uedaccel));
            yvect = -((mousy*(int32_t)sintable[(ang+2048)&2047])<<(3+pk_uedaccel));

            move_and_update(xvect, yvect, 0);
        }
        else if (!mlook && (bstatus&(1|2|4))==2)
        {
            mlook=2;
        }
        else if ((bstatus&(1|2|4))==(1|2))
        {
            zmode = 2;
            xvect = -((mousx*(int32_t)sintable[(ang+2048)&2047])<<pk_uedaccel);
            yvect = -((mousx*(int32_t)sintable[(ang+1536)&2047])<<pk_uedaccel);
            pos.z += mousy<<(4+pk_uedaccel);

            move_and_update(xvect, yvect, 0);
        }
        else if ((bstatus&(1|2|4))==4)
        {
            zmode = 2;

            // horiz-100 of 200 is viewing at 326.4 build angle units (=atan(200/128)) upward
            tempint = getangle(128, horiz-100);

            xvect = -((mousy*
                       ((int32_t)sintable[(ang+2560)&2047]>>6)*
                       ((int32_t)sintable[(tempint+512)&2047])>>6)
                      <<pk_uedaccel);
            yvect = -((mousy*
                       ((int32_t)sintable[(ang+2048)&2047]>>6)*
                       ((int32_t)sintable[(tempint+512)&2047])>>6)
                      <<pk_uedaccel);

            pos.z += mousy*(((int32_t)sintable[(tempint+2048)&2047])>>(10-pk_uedaccel));

            move_and_update(xvect, yvect, 2);
        }
    }

    if (mskip)
    {
        // mskip was set in astub.c to not trigger UEd mouse movements.
        // Reset now.
        mskip = 0;
    }
    else
    {
        if (mlook && (unrealedlook==0 || (bstatus&(1|4))==0))
        {
            ang += mousx;
            horiz -= mousy;

            /*
            if (mousy && !(mousy/4))
                horiz--;
            if (mousx && !(mousx/2))
                ang++;
            */


            inpclamp(&horiz, -99, 299);

            if (mlook == 1)
            {
                searchx = xdim>>1;
                searchy = ydim>>1;
            }
            osearchx = searchx-mousx;
            osearchy = searchy-mousy;

            if (mousx || mousy)
            {
                silentmessage("x:%d y:%d z:%d ang:%d horiz:%d", pos.x, pos.y, pos.z, ang, horiz);
                getmessagetimeoff = (int32_t) totalclock+30;
            }
        }
        else if (unrealedlook==0 || (bstatus&(1|2|4))==0)
        {
            osearchx = searchx;
            osearchy = searchy;
            searchx += mousx;
            searchy += mousy;

            inpclamp(&searchx, 12, xdim-13);
            inpclamp(&searchy, 12, ydim-13);
        }
    }

//    showmouse();

    if (keystatus[sc_F9])  // F9
    {
        if (mhk)
        {
            Bmemset(spriteext, 0, sizeof(spriteext_t) * MAXSPRITES);
            Bmemset(spritesmooth, 0, sizeof(spritesmooth_t) * (MAXSPRITES+MAXUNIQHUDID));
            engineClearLightsFromMHK();
            mhk = 0;
            message("Maphacks disabled");
        }
        else
        {
            mhk = 1;
            loadmhk(1);
        }

        keystatus[sc_F9] = 0;
    }

    mainloop_move();

    getzrange(&pos,cursectnum, &hiz,&hihit, &loz,&lohit, 128, (m32_clipping==1)?0:CLIPMASK0);
/*
{
    int32_t his = !(hihit&32768), los = !(lohit&32768);
    if (m32_numdebuglines<64)
        Bsprintf(m32_debugstr[m32_numdebuglines++], "s%d: cf[%s%d, %s%d] z(%d, %d)", cursectnum,
                 his?"s":"w",hihit&16383, los?"s":"w",lohit&16383, hiz,loz);
}
*/

    oposz = pos.z;
    if (zmode == 0)
    {
        goalz = loz-(kensplayerheight<<8);  //playerheight pixels above floor
        if (goalz < hiz+(16<<8))  //ceiling&floor too close
            goalz = (loz+hiz)>>1;
        goalz += mousz;

        if (DOWN_BK(MOVEUP))  //A (stand high)
        {
            goalz -= (16<<8);
            if (DOWN_BK(RUN))
                goalz -= (24<<8);
        }
        if (DOWN_BK(MOVEDOWN))  //Z (stand low)
        {
            goalz += (12<<8);
            if (DOWN_BK(RUN))
                goalz += (12<<8);
        }

        if (goalz != pos.z)
        {
            if (pos.z < goalz) hvel += 64;
            if (pos.z > goalz) hvel = ((goalz-pos.z)>>3);

            pos.z += hvel;
            if (pos.z > loz-(4<<8)) pos.z = loz-(4<<8), hvel = 0;
            if (pos.z < hiz+(4<<8)) pos.z = hiz+(4<<8), hvel = 0;
        }
    }
    else
    {
        goalz = pos.z;
        if (DOWN_BK(MOVEUP))  //A
        {
            if (eitherALT)
            {
                horiz = max(-100,horiz-((DOWN_BK(RUN)+1)*synctics*2));
            }
            else
            {
                if (zmode != 1)
                    goalz -= (8<<8);
                else
                {
                    zlock += (4<<8);
                    DOWN_BK(MOVEUP) = 0;
                }
            }
        }
        if (DOWN_BK(MOVEDOWN))  //Z (stand low)
        {
            if (eitherALT)
            {
                horiz = min(300,horiz+((DOWN_BK(RUN)+1)*synctics*2));
            }
            else
            {
                if (zmode != 1)
                    goalz += (8<<8);
                else if (zlock > 0)
                {
                    zlock -= (4<<8);
                    DOWN_BK(MOVEDOWN) = 0;
                }
            }
        }

        if (m32_clipping)
            inpclamp(&goalz, hiz+(4<<8), loz-(4<<8));

        if (zmode == 1) goalz = loz-zlock;
        if (m32_clipping && (goalz < hiz+(4<<8)))
            goalz = ((loz+hiz)>>1);  //ceiling&floor too close
        if (zmode == 1) pos.z = goalz;

        if (goalz != pos.z)
        {
            //if (pos.z < goalz) hvel += (32<<DOWN_BK(RUN));
            //if (pos.z > goalz) hvel -= (32<<DOWN_BK(RUN));
            if (pos.z < goalz)
                hvel = ((192*synctics)<<DOWN_BK(RUN));
            else
                hvel = -((192*synctics)<<DOWN_BK(RUN));

            pos.z += hvel;

            if (m32_clipping)
            {
                if (pos.z > loz-(4<<8)) pos.z = loz-(4<<8), hvel = 0;
                if (pos.z < hiz+(4<<8)) pos.z = hiz+(4<<8), hvel = 0;
            }
        }
        else
            hvel = 0;
    }

    {
        int16_t ocursectnum = cursectnum;
        updatesectorz(pos.x,pos.y,pos.z, &cursectnum);
        if (cursectnum<0)
        {
            if (zmode != 2)
                pos.z = oposz;  // don't allow to fall into infinity when in void space
            cursectnum = ocursectnum;
        }
    }

    if (pos.z != oposz && in3dmode())
    {
        silentmessage("x:%d y:%d z:%d ang:%d horiz:%d", pos.x, pos.y, pos.z, ang, horiz);
        getmessagetimeoff = (int32_t) totalclock+30;
    }

    searchit = 2;
    if (searchstat >= 0)
    {
        if ((bstatus&(1|2|4)) || keystatus[sc_Space])  // SPACE
            searchit = 0;

        if (keystatus[sc_S])  //S (insert sprite) (3D)
        {
            hitdata_t hit;
            vec2_t osearch = {searchx, searchy};
            vec2_t bdim = {xdim, ydim};
            if (m32_is2d3dmode())
            {
                xdim = XSIZE_2D3D;
                ydim = YSIZE_2D3D;
                searchx -= m32_2d3d.x;
                searchy -= m32_2d3d.y;
            }

            vec2_t da = { 16384, divscale14(searchx-(xdim>>1), xdim>>1) };

            rotatepoint(zerovec, da, ang, &da);

#ifdef USE_OPENGL
            if (videoGetRenderMode() == REND_POLYMOST)
                hit = polymost_hitdata;
            else
#endif
                hitscan((const vec3_t *)&pos,cursectnum,              //Start position
                    da.x,da.y,(scale(searchy,200,ydim)-horiz)*2000, //vector of 3D ang
                    &hit,CLIPMASK1);

            if (hit.sect >= 0)
            {
                da.x = hit.pos.x;
                da.y = hit.pos.y;
                if (gridlock && grid > 0)
                {
                    if (AIMING_AT_WALL || AIMING_AT_MASKWALL)
                        hit.pos.z &= 0xfffffc00;
                    else
                        locktogrid(&da.x, &da.y);
                }

                i = insert_sprite_common(hit.sect, da.x, da.y);

                if (i < 0)
                    message("Couldn't insert sprite.");
                else
                {
                    int32_t cz, fz;

                    handle_sprite_in_clipboard(i);

                    spriteoncfz(i, &cz, &fz);
                    sprite[i].z = clamp2(hit.pos.z, cz, fz);

                    if (AIMING_AT_WALL || AIMING_AT_MASKWALL)
                    {
                        sprite[i].cstat &= ~48;
                        sprite[i].cstat |= (16+64);

                        correct_ornamented_sprite(i, hit.wall);
                    }
                    else
                        sprite[i].cstat |= (tilesiz[sprite[i].picnum].y>=32);

                    correct_sprite_yoffset(i);

                    asksave = 1;

                    VM_OnEvent(EVENT_INSERTSPRITE3D, i);
                }
            }

            xdim = bdim.x;
            ydim = bdim.y;
            searchx = osearch.x;
            searchy = osearch.y;
            keystatus[sc_S] = 0;
        }

        if (keystatus[sc_F5]||keystatus[sc_F6])  //F5,F6
        {
            switch (searchstat)
            {
            case SEARCH_CEILING:
            case SEARCH_FLOOR:
                CallExtShowSectorData(searchsector); break;
            case SEARCH_WALL:
            case SEARCH_MASKWALL:
                CallExtShowWallData(searchwall); break;
            case SEARCH_SPRITE:
                CallExtShowSpriteData(searchwall); break;
            }

            keystatus[sc_F5] = keystatus[sc_F6] = 0;
        }
        if (keystatus[sc_F7]||keystatus[sc_F8])  //F7,F8
        {
            switch (searchstat)
            {
            case SEARCH_CEILING:
            case SEARCH_FLOOR:
                CallExtEditSectorData(searchsector); break;
            case SEARCH_WALL:
            case SEARCH_MASKWALL:
                CallExtEditWallData(searchwall); break;
            case SEARCH_SPRITE:
                CallExtEditSpriteData(searchwall); break;
            }

            keystatus[sc_F7] = keystatus[sc_F8] = 0;
        }

    }

    if (keystatus[buildkeys[BK_MODE2D_3D]] && !m32_is2d3dmode())  // Enter
    {

        vid_gamma_3d = g_videoGamma;
        vid_contrast_3d = g_videoContrast;
        vid_brightness_3d = g_videoBrightness;

        g_videoGamma = g_videoContrast = 1.0;
        g_videoBrightness = 0.0;

        videoSetPalette(0,0,0);

        keystatus[buildkeys[BK_MODE2D_3D]] = 0;
        overheadeditor();
        keystatus[buildkeys[BK_MODE2D_3D]] = 0;

        g_videoGamma = vid_gamma_3d;
        g_videoContrast = vid_contrast_3d;
        g_videoBrightness = vid_brightness_3d;

        vid_gamma_3d = vid_contrast_3d = vid_brightness_3d = -1;

        videoSetPalette(GAMMA_CALC,0,0);
    }
}

char changechar(char dachar, int32_t dadir, char smooshyalign, char boundcheck)
{
    if (dadir < 0)
    {
        if ((dachar > 0) || (boundcheck == 0))
        {
            dachar--;
            if (smooshyalign > 0)
                dachar = (dachar&0xf8);
        }
    }
    else if (dadir > 0)
    {
        if ((dachar < 255) || (boundcheck == 0))
        {
            dachar++;
            if (smooshyalign > 0)
            {
                if (dachar >= 256-8) dachar = 255;
                else dachar = ((dachar+7)&0xf8);
            }
        }
    }
    return dachar;
}


////////////////////// OVERHEADEDITOR //////////////////////

// some 2d mode state
static struct overheadstate
{
    // number of backed up drawn walls
    int32_t bak_wallsdrawn;

    // state related to line drawing
    int16_t suckwall, split;
    int16_t splitsect;
    int16_t splitstartwall;
} ovh;


static int32_t inside_editor(const vec3_t *pos, int32_t searchx, int32_t searchy, int32_t zoom,
                             int32_t x, int32_t y, int16_t sectnum)
{
    if (!m32_sideview)
        return inside(x, y, sectnum);

    // if in side-view mode, use the screen coords instead
    {
        int32_t dst = MAXSECTORS+M32_FIXME_SECTORS-1, i, oi;
        int32_t srcw=sector[sectnum].wallptr, dstw=MAXWALLS;
        int32_t ret;

        if (sector[sectnum].wallnum > M32_FIXME_WALLS)
            return -1;

        Bmemcpy(&sector[dst], &sector[sectnum], sizeof(sectortype));
        sector[dst].wallptr = dstw;

        Bmemcpy(&wall[dstw], &wall[srcw], sector[dst].wallnum*sizeof(walltype));
        for (i=dstw, oi=srcw; i<dstw+sector[dst].wallnum; i++, oi++)
        {
            wall[i].point2 += dstw-srcw;

            editorGet2dScreenCoordinates(&wall[i].x, &wall[i].y, wall[i].x-pos->x, wall[i].y-pos->y, zoom);
            wall[i].y += getscreenvdisp(getflorzofslope(sectnum,wall[oi].x,wall[oi].y)-pos->z, zoom);
            wall[i].x += halfxdim16;
            wall[i].y += midydim16;
        }

        i = numsectors;
        numsectors = dst+1;
        ret = inside(searchx, searchy, dst);
        numsectors = i;
        return ret;
    }
}

int32_t inside_editor_curpos(int16_t sectnum)
{
    // TODO: take care: mous[xy]plc global vs overheadeditor auto
    return inside_editor(&pos, searchx,searchy, zoom, mousxplc,mousyplc, sectnum);
}


static inline void drawline16base(int32_t bx, int32_t by, int32_t x1, int32_t y1, int32_t x2, int32_t y2, char col)
{
    editorDraw2dLine(bx+x1, by+y1, bx+x2, by+y2, col);
}

void drawsmallabel(const char *text, char col, char backcol, char border, int32_t dax, int32_t day, int32_t daz)
{
    editorGet2dScreenCoordinates(&dax,&day, dax-pos.x,day-pos.y, zoom);

    if (m32_sideview)
        day += getscreenvdisp(daz-pos.z, zoom);

    int32_t const x1 = halfxdim16+dax-(Bstrlen(text)<<1);
    int32_t const y1 = midydim16+day-4;
    int32_t const x2 = x1 + (Bstrlen(text)<<2)+2;
    int32_t const y2 = y1 + 7;

    int f = mulscale8(x2-x1, zoom);

    if ((x1 <= -f) || (x2 >= xdim + f) || (y1 <= -f) || (y2 >= ydim16 + f))
        return;

    printext16(x1,y1, col,backcol, text,1);

    editorDraw2dLine(x1-2, y1-2, x2-2, y1-2, border);
    editorDraw2dLine(x1-2, y2+1, x2-2, y2+1, border);

    editorDraw2dLine(x1-3, y1-1, x1-3, y2+0, border);
    editorDraw2dLine(x2-1, y1-1, x2-1, y2+0, border);

    editorDraw2dLine(x1-1,y1-1, x2-3,y1-1, backcol);
    editorDraw2dLine(x1-1,y2+0, x2-3,y2+0, backcol);

    editorDraw2dLine(x1-2,y1+0, x1-2,y2-1, backcol);
    editorDraw2dLine(x2-2,y1+0, x2-2,y2-1, backcol);
    editorDraw2dLine(x2-3,y1+0, x2-3,y2+0, backcol);

    videoBeginDrawing(); //{{{

    if ((unsigned)y1-1 < ydim16+0u && (unsigned) (x1-2) < xdim2d+0u && (unsigned) (x2-2) < xdim2d+0u)
    {
        drawpixel((char *) (frameplace + ((y1-1) * bytesperline) + (x1-2)), border);
        drawpixel((char *) (frameplace + ((y1-1) * bytesperline) + (x2-2)), border);
    }

    if ((unsigned) y2 < ydim16+0u && (unsigned) (x1-2) < xdim2d+0u && (unsigned) (x2-2) < xdim2d+0u)
    {
        drawpixel((char *) (frameplace + ((y2) * bytesperline) + (x1-2)), border);
        drawpixel((char *) (frameplace + ((y2) * bytesperline) + (x2-2)), border);
    }

    videoEndDrawing();
}

// backup highlighted sectors with sprites as mapinfo for later restoration
// return values:
//  -1: highlightsectorcnt<=0
//   0: ok
static int32_t backup_highlighted_map(mapinfofull_t *mapinfo)
{
    int32_t i, j, k, m, tmpnumwalls=0, tmpnumsprites=0;
    int16_t *const otonsect = (int16_t *)tempxyar;  // STRICTALIASING
    int16_t *const otonwall = ((int16_t *)tempxyar) + MAXWALLS;
#ifdef YAX_ENABLE
    int16_t otonbunch[YAX_MAXBUNCHES];
    int16_t numsectsofbunch[YAX_MAXBUNCHES];  // ceilings + floors
#endif

    if (highlightsectorcnt <= 0)
        return -1;

#ifdef YAX_ENABLE
    for (i=0; i<numyaxbunches; i++)
        numsectsofbunch[i] = 0;
#endif

    // set up old-->new mappings
    j = 0;
    k = 0;
    for (i=0; i<numsectors; i++)
    {
        int32_t startwall, endwall;

        if (hlsectorbitmap[i>>3]&pow2char[i&7])
        {
#ifdef YAX_ENABLE
            int16_t bn[2], cf;

            yax_getbunches(i, &bn[0], &bn[1]);
            for (cf=0; cf<2; cf++)
                if (bn[cf] >= 0)
                    numsectsofbunch[bn[cf]]++;
#endif
            otonsect[i] = j++;

            for (WALLS_OF_SECTOR(i, m))
                otonwall[m] = k++;
        }
        else
        {
            otonsect[i] = -1;

            for (WALLS_OF_SECTOR(i, m))
                otonwall[m] = -1;
        }
    }

#ifdef YAX_ENABLE
    j = 0;
    for (i=0; i<numyaxbunches; i++)
    {
        // only back up complete bunches
        if (numsectsofbunch[i] == yax_numsectsinbunch(i, 0)+yax_numsectsinbunch(i, 1))
            otonbunch[i] = j++;  // kept bunch
        else
            otonbunch[i] = -1;  // discarded bunch
    }
    mapinfo->numyaxbunches = j;
#endif

    // count walls & sprites
    for (i=0; i<highlightsectorcnt; i++)
    {
        tmpnumwalls += sector[highlightsector[i]].wallnum;

        m = headspritesect[highlightsector[i]];
        while (m != -1)
        {
            tmpnumsprites++;
            m = nextspritesect[m];
        }
    }

    // allocate temp storage
    mapinfo->sector = (usectortype *)Xmalloc(highlightsectorcnt * sizeof(sectortype));
    mapinfo->wall = (uwalltype *)Xmalloc(tmpnumwalls * sizeof(walltype));

#ifdef YAX_ENABLE
    if (mapinfo->numyaxbunches > 0)
    {
        mapinfo->bunchnum = (int16_t *)Xmalloc(highlightsectorcnt*2*sizeof(int16_t));
        mapinfo->ynextwall = (int16_t *)Xmalloc(tmpnumwalls*2*sizeof(int16_t));
    }
    else
    {
        mapinfo->bunchnum = mapinfo->ynextwall = NULL;
    }
#endif

    if (tmpnumsprites>0)
    {
        mapinfo->sprite = (uspritetype *)Xmalloc(tmpnumsprites * sizeof(spritetype));
    }
    else
    {
        // would never be accessed because mapinfo->numsprites is 0, but cleaner
        mapinfo->sprite = NULL;
    }


    // copy everything over
    tmpnumwalls = 0;
    tmpnumsprites = 0;
    for (i=0; i<highlightsectorcnt; i++)
    {
        k = highlightsector[i];
        Bmemcpy(&mapinfo->sector[i], &sector[k], sizeof(sectortype));
        mapinfo->sector[i].wallptr = tmpnumwalls;

#ifdef YAX_ENABLE
        if (mapinfo->numyaxbunches > 0 || numyaxbunches > 0)
        {
            int16_t bn[2];

            yax_getbunches(k, &bn[0], &bn[1]);
            for (j=0; j<2; j++)
            {
                // old bunchnum, new bunchnum
                int32_t obn=bn[j], nbn=(obn>=0) ? otonbunch[obn] : -1;

                if (mapinfo->numyaxbunches > 0)
                    mapinfo->bunchnum[2*i + j] = nbn;

                if (obn >= 0 && nbn < 0)
                {
                    // A bunch was discarded.
                    auto const sec = &mapinfo->sector[i];
# if !defined NEW_MAP_FORMAT
                    uint16_t *const cs = j==YAX_CEILING ? &sec->ceilingstat : &sec->floorstat;
                    uint8_t *const xp = j==YAX_CEILING ? &sec->ceilingxpanning : &sec->floorxpanning;

                    *cs &= ~YAX_BIT;
                    *xp = 0;
# else
                    if (j == YAX_CEILING)
                        sec->ceilingbunch = -1;
                    else
                        sec->floorbunch = -1;
# endif
                }
            }
        }
#endif

        for (j=0; j<sector[k].wallnum; j++)
        {
            m = sector[k].wallptr;
            Bmemcpy(&mapinfo->wall[tmpnumwalls+j], &wall[m+j], sizeof(walltype));
            mapinfo->wall[tmpnumwalls+j].point2 += (tmpnumwalls-m);

#ifdef YAX_ENABLE
            if (mapinfo->numyaxbunches > 0 || numyaxbunches > 0)
            {
                int32_t cf;

                for (cf=0; cf<2; cf++)
                {
                    const int32_t ynw = yax_getnextwall(m+j, cf);
                    const int32_t nynw = (ynw >= 0) ? otonwall[ynw] : -1;

                    if (mapinfo->numyaxbunches > 0)
                        mapinfo->ynextwall[2*(tmpnumwalls+j) + cf] = nynw;

                    if (ynw >= 0 && nynw < 0)  // CLEAR_YNEXTWALLS
                        YAX_PTRNEXTWALL(mapinfo->wall, tmpnumwalls+j, cf) = YAX_NEXTWALLDEFAULT(cf);
                }
            }
#endif
            m = mapinfo->wall[tmpnumwalls+j].nextsector;
            if (m < 0 || otonsect[m] < 0)
            {
                mapinfo->wall[tmpnumwalls+j].nextsector = -1;
                mapinfo->wall[tmpnumwalls+j].nextwall = -1;
            }
            else
            {
                mapinfo->wall[tmpnumwalls+j].nextsector = otonsect[m];
                m = mapinfo->wall[tmpnumwalls+j].nextwall;
                mapinfo->wall[tmpnumwalls+j].nextwall = otonwall[m];
            }
        }
        tmpnumwalls += j;

        m = headspritesect[highlightsector[i]];
        while (m != -1)
        {
            Bmemcpy(&mapinfo->sprite[tmpnumsprites], &sprite[m], sizeof(spritetype));
            mapinfo->sprite[tmpnumsprites].sectnum = otonsect[highlightsector[i]];
            m = nextspritesect[m];
            tmpnumsprites++;
        }
    }


    mapinfo->numsectors = highlightsectorcnt;
    mapinfo->numwalls = tmpnumwalls;
    mapinfo->numsprites = tmpnumsprites;

    return 0;
}

static void mapinfofull_free(mapinfofull_t *mapinfo)
{
    Xfree(mapinfo->sector);
#ifdef YAX_ENABLE
    if (mapinfo->numyaxbunches > 0)
    {
        Xfree(mapinfo->bunchnum);
        Xfree(mapinfo->ynextwall);
    }
#endif
    Xfree(mapinfo->wall);
    if (mapinfo->numsprites>0)
        Xfree(mapinfo->sprite);
}

// restore map saved with backup_highlighted_map, also
// frees mapinfo's sector, wall, (sprite) in any case.
// return values:
//  -1: limits exceeded
//   0: ok
// forreal: if 0, only test if we have enough space (same return values)
static int32_t restore_highlighted_map(mapinfofull_t *mapinfo, int32_t forreal)
{
    int32_t i, j, onumsectors=numsectors, newnumsectors, newnumwalls;

    if (numsectors+mapinfo->numsectors>MAXSECTORS || numwalls+mapinfo->numwalls>MAXWALLS
#ifdef YAX_ENABLE
            || numyaxbunches+mapinfo->numyaxbunches > YAX_MAXBUNCHES
#endif
            || Numsprites+mapinfo->numsprites>MAXSPRITES)
    {
        mapinfofull_free(mapinfo);
        return -1;
    }

    if (!forreal)
        return 0;

    newnumsectors = numsectors + mapinfo->numsectors;
    newnumwalls = numwalls + mapinfo->numwalls;

    // copy sectors & walls
    Bmemcpy(&sector[numsectors], mapinfo->sector, mapinfo->numsectors*sizeof(sectortype));
    Bmemcpy(&wall[numwalls], mapinfo->wall, mapinfo->numwalls*sizeof(walltype));

    // tweak index members
    for (i=numwalls; i<newnumwalls; i++)
    {
        wall[i].point2 += numwalls;

        if (wall[i].nextsector >= 0)
        {
            wall[i].nextsector += numsectors;
            wall[i].nextwall += numwalls;
        }
#ifdef YAX_ENABLE
        for (j=0; j<2; j++)
        {
            if (mapinfo->numyaxbunches > 0)
            {
                yax_setnextwall(i, j, mapinfo->ynextwall[2*(i-numwalls) + j]>=0 ?
                                numwalls+mapinfo->ynextwall[2*(i-numwalls) + j] : -1);
            }
            else
            {
# if !defined NEW_MAP_FORMAT
                // XXX: When copying a TROR portion into a non-TROR map (e.g. a
                // new one), tags denoting ynextwalls are left in place.
                wall[i].cstat &= ~YAX_NEXTWALLBIT(j);  // CLEAR_YNEXTWALLS
# else
                yax_setnextwall(i, j, -1);
# endif
            }
        }
#endif
    }
    for (i=numsectors; i<newnumsectors; i++)
        sector[i].wallptr += numwalls;

    // highlight copied sectors

    numsectors = newnumsectors;

    Bmemset(hlsectorbitmap, 0, sizeof(hlsectorbitmap));
    for (i=onumsectors; i<newnumsectors; i++)
    {
        hlsectorbitmap[i>>3] |= pow2char[i&7];

#ifdef YAX_ENABLE
        for (j=0; j<2; j++)
        {
            if (mapinfo->numyaxbunches > 0)
            {
                int32_t bn = mapinfo->bunchnum[2*(i-onumsectors)+j];
                yax_setbunch(i, j, bn>=0 ? numyaxbunches+bn : -2);
                // -2 clears forward yax-nextwall links.
                // XXX: still may wrongly reset xpanning.
            }
            else
                Bassert(yax_getbunch(i, j) < 0);
        }
#endif
    }

    // insert sprites
    for (i=0; i<mapinfo->numsprites; i++)
    {
        uspriteptr_t srcspr = &mapinfo->sprite[i];
        int32_t sect = onumsectors + srcspr->sectnum;

        j = insertsprite(sect, srcspr->statnum);
        Bassert(j >= 0);
        Bmemcpy(&sprite[j], srcspr, sizeof(spritetype));
        sprite[j].sectnum = sect;
    }

    mapinfofull_free(mapinfo);

    numwalls = newnumwalls;

    update_highlightsector();

#ifdef YAX_ENABLE
    if (mapinfo->numyaxbunches > 0)
        yax_update(0);
#endif
    yax_updategrays(pos.z);

    return 0;
}


static int16_t newnumwalls=-1;

void ovh_whiteoutgrab(int32_t restoreredwalls)
{
    int32_t i, j, k, startwall, endwall;
#if 0
//def YAX_ENABLE
    int16_t cb, fb;
#endif

    if (restoreredwalls)
    {
        // restore onextwalls first
        for (i=0; i<numsectors; i++)
            for (WALLS_OF_SECTOR(i, j))
                checksectorpointer(j, i);
    }

    for (i=0; i<MAXWALLS; i++)
        onextwall[i] = -1;

    //White out all bordering lines of grab that are
    //not highlighted on both sides
    for (i=highlightsectorcnt-1; i>=0; i--)
        for (WALLS_OF_SECTOR(highlightsector[i], j))
        {
            if (wall[j].nextwall < 0)
                continue;

            k = wall[j].nextsector;

            if (hlsectorbitmap[k>>3]&pow2char[k&7])
                continue;
#if 0
//def YAX_ENABLE
            // internal red walls are kept red
            yax_getbunches(highlightsector[i], &cb, &fb);
            if (cb>=0 && yax_getbunch(k, YAX_CEILING)>=0)
                continue;
            if (fb>=0 && yax_getbunch(k, YAX_FLOOR)>=0)
                continue;
#endif
            onextwall[j] = wall[j].nextwall;

            NEXTWALL(j).nextwall = -1;
            NEXTWALL(j).nextsector = -1;
            wall[j].nextwall = -1;
            wall[j].nextsector = -1;
        }

    if (highlightsectorcnt > 0)
        mkonwvalid();
    else
        mkonwinvalid_keeptempsect();
}

static void duplicate_selected_sectors(void)
{
    mapinfofull_t mapinfo;
    int32_t i, j, onumsectors;
#ifdef YAX_ENABLE
    int32_t onumyaxbunches;
#endif
    int32_t minx=INT32_MAX, maxx=INT32_MIN, miny=INT32_MAX, maxy=INT32_MIN, dx, dy;

    i = backup_highlighted_map(&mapinfo);

    if (i < 0)
    {
        message("Out of memory!");
        return;
    }

    i = restore_highlighted_map(&mapinfo, 0);
    if (i < 0)
    {
        // XXX: no, might be another limit too.  Better message needed.
        printmessage16("Copying sectors would exceed sector or wall limit.");
        return;
    }

    // restoring would succeed, tweak things...
    Bmemset(hlsectorbitmap, 0, sizeof(hlsectorbitmap));
    for (i=0; i<highlightsectorcnt; i++)
    {
        int32_t startwall, endwall;

        // first, make red lines of old selected sectors, effectively
        // restoring the original state
        for (WALLS_OF_SECTOR(highlightsector[i], j))
        {
            if (wall[j].nextwall >= 0)
                checksectorpointer(wall[j].nextwall,wall[j].nextsector);
            checksectorpointer(j, highlightsector[i]);

            minx = min(minx, TrackerCast(wall[j].x));
            maxx = max(maxx, TrackerCast(wall[j].x));
            miny = min(miny, TrackerCast(wall[j].y));
            maxy = max(maxy, TrackerCast(wall[j].y));
        }
    }

    // displace walls & sprites of new sectors by a small amount:
    // calculate displacement
    if (grid>0 && grid<9)
        dx = max(2048>>grid, 128);
    else
        dx = 512;
    dy = -dx;
    if (maxx+dx >= editorgridextent) dx*=-1;
    if (minx+dx <= -editorgridextent) dx*=-1;
    if (maxy+dy >= editorgridextent) dy*=-1;
    if (miny+dy <= -editorgridextent) dy*=-1;

    onumsectors = numsectors;
#ifdef YAX_ENABLE
    onumyaxbunches = numyaxbunches;
#endif
    // restore! this will not fail.
    restore_highlighted_map(&mapinfo, 1);

    // displace
    for (i=onumsectors; i<numsectors; i++)
    {
        for (j=sector[i].wallptr; j<sector[i].wallptr+sector[i].wallnum; j++)
        {
            wall[j].x += dx;
            wall[j].y += dy;
        }

        for (j=headspritesect[i]; j>=0; j=nextspritesect[j])
        {
            sprite[j].x += dx;
            sprite[j].y += dy;
        }
    }

#ifdef YAX_ENABLE
    if (numyaxbunches > onumyaxbunches)
        printmessage16("Sectors duplicated, creating %d new bunches.", numyaxbunches-onumyaxbunches);
    else
#endif
        printmessage16("Sectors duplicated.");
    asksave = 1;

#ifdef YAX_ENABLE
    if (numyaxbunches > onumyaxbunches)
        yax_update(0);
#endif
    yax_updategrays(pos.z);
}


static void duplicate_selected_sprites(void)
{
    int32_t i, j, k=0;

    for (i=0; i<highlightcnt; i++)
        if ((highlight[i]&0xc000) == 16384)
            k++;

    if (Numsprites + k <= MAXSPRITES)
    {
        for (i=0; i<highlightcnt; i++)
            if ((highlight[i]&0xc000) == 16384)
            {
                //duplicate sprite
                k = (highlight[i]&16383);
                j = insertsprite(sprite[k].sectnum,sprite[k].statnum);
                Bmemcpy(&sprite[j],&sprite[k],sizeof(spritetype));
//                sprite[j].sectnum = sprite[k].sectnum;   //Don't let memcpy overwrite sector!
//                setsprite(j,(vec3_t *)&sprite[j]);
            }

        printmessage16("Sprites duplicated.");
        asksave = 1;
    }
    else
    {
        printmessage16("Copying sprites would exceed sprite limit.");
    }
}

static void correct_ornamented_sprite(int32_t i, int32_t hitw)
{
    int32_t j;

    if (hitw >= 0)
    {
        sprite[i].ang = (getangle(POINT2(hitw).x-wall[hitw].x,
                                  POINT2(hitw).y-wall[hitw].y)+512)&2047;

        //Make sure sprite's in right sector
        if (inside(sprite[i].x, sprite[i].y, sprite[i].sectnum) != 1)
        {
            j = wall[hitw].point2;
            sprite[i].x -= ksgn(wall[j].y-wall[hitw].y);
            sprite[i].y += ksgn(wall[j].x-wall[hitw].x);
        }
    }
}

void DoSpriteOrnament(int32_t i)
{
    hitdata_t hit;

    hitscan((const vec3_t *)&sprite[i],sprite[i].sectnum,
            sintable[(sprite[i].ang+1536)&2047],
            sintable[(sprite[i].ang+1024)&2047],
            0,
            &hit,CLIPMASK1);

    if (hit.sect == -1)
        return;

    sprite[i].x = hit.pos.x;
    sprite[i].y = hit.pos.y;
    sprite[i].z = hit.pos.z;
    changespritesect(i, hit.sect);

    correct_ornamented_sprite(i, hit.wall);
}

void update_highlight(void)
{
    int32_t i;

    highlightcnt = 0;
    for (i=0; i<numwalls; i++)
        if (show2dwall[i>>3]&pow2char[i&7])
            highlight[highlightcnt++] = i;
    for (i=0; i<MAXSPRITES; i++)
        if (sprite[i].statnum < MAXSTATUS)
        {
            if (show2dsprite[i>>3]&pow2char[i&7])
                highlight[highlightcnt++] = i+16384;
        }
        else
            show2dsprite[i>>3] &= ~pow2char[i&7];

    if (highlightcnt == 0)
        highlightcnt = -1;
}

void update_highlightsector(void)
{
    int32_t i;

    minhlsectorfloorz = INT32_MAX;
    numhlsecwalls = 0;

    highlightsectorcnt = 0;
    for (i=0; i<numsectors; i++)
        if (hlsectorbitmap[i>>3]&pow2char[i&7])
        {
            highlightsector[highlightsectorcnt++] = i;
            minhlsectorfloorz = min(minhlsectorfloorz, TrackerCast(sector[i].floorz));
            numhlsecwalls += sector[i].wallnum;
        }

    if (highlightsectorcnt==0)
    {
        minhlsectorfloorz = 0;
        highlightsectorcnt = -1;
    }
}

// Get average point of sectors
static void get_sectors_center(const int16_t *sectors, int32_t numsecs, int32_t *cx, int32_t *cy)
{
    int32_t i, j, k=0, dax = 0, day = 0;
    int32_t startwall, endwall;

    for (i=0; i<numsecs; i++)
    {
        for (WALLS_OF_SECTOR(sectors[i], j))
        {
            dax += wall[j].x;
            day += wall[j].y;
            k++;
        }
    }

    if (k > 0)
    {
        dax /= k;
        day /= k;
    }

    *cx = dax;
    *cy = day;
}

static int32_t insert_sprite_common(int32_t sectnum, int32_t dax, int32_t day)
{
    int32_t i, j, k;

    i = insertsprite(sectnum,0);
    if (i < 0)
        return -1;

    sprite[i].x = dax, sprite[i].y = day;
    sprite[i].cstat = defaultspritecstat;
    sprite[i].shade = 0;
    sprite[i].pal = 0;
    sprite[i].xrepeat = 64, sprite[i].yrepeat = 64;
    sprite[i].xoffset = 0, sprite[i].yoffset = 0;
    sprite[i].ang = 1536;
    sprite[i].xvel = 0; sprite[i].yvel = 0; sprite[i].zvel = 0;
    sprite[i].owner = -1;
    sprite[i].clipdist = 32;
    sprite[i].lotag = 0;
    sprite[i].hitag = 0;
    sprite[i].extra = -1;

    Bmemset(localartfreq, 0, sizeof(localartfreq));
    for (k=0; k<MAXSPRITES; k++)
        if (sprite[k].statnum < MAXSTATUS && k!=i)
            localartfreq[sprite[k].picnum]++;

    j = 0;
    for (k=0; k<MAXTILES; k++)
        if (localartfreq[k] > localartfreq[j])
            j = k;

    if (localartfreq[j] > 0)
        sprite[i].picnum = j;
    else
        sprite[i].picnum = 0;

    return i;
}

void correct_sprite_yoffset(int32_t i)
{
    int32_t tileyofs = picanm[sprite[i].picnum].yofs;
    int32_t tileysiz = tilesiz[sprite[i].picnum].y;

    if (klabs(tileyofs) >= tileysiz)
    {
        tileyofs *= -1;
        if (tileyofs == 128)
            tileyofs = 127;

        sprite[i].yoffset = tileyofs;
    }
    else
        sprite[i].yoffset = 0;
}

// keepcol >= 0 && <256: keep that idx-color
// keepcol < 0: keep none
// keepcol >= 256: 0x00ffffff is mask for 3 colors
void fade_editor_screen(int32_t keepcol)
{
    char blackcol=0, greycol=whitecol-25, *cp;
    int32_t pix, i, threecols = (keepcol >= 256);
    char cols[3] = {(char)(keepcol&0xff), (char)((keepcol>>8)&0xff), (char)((keepcol>>16)&0xff)};

    videoBeginDrawing();
    cp = (char *)frameplace;
    for (i=0; i<bytesperline*(ydim-STATUS2DSIZ2); i++, cp++)
    {
        pix = (uint8_t)(*cp);

        if (!threecols && pix == keepcol)
            continue;
        if (threecols)
            if (pix==cols[0] || pix==cols[1] || pix==cols[2])
                continue;

        if (*cp==greycol)
            *cp = blackcol;
        else if (*cp != blackcol)
            *cp = greycol;
    }
    videoEndDrawing();
    videoShowFrame(1);
}

static void copy_some_wall_members(int16_t dst, int16_t src, int32_t reset_some)
{
    static uwalltype nullwall;
    walltype * const dstwal = &wall[dst];
    auto const srcwal = src >= 0 ? (uwallptr_t)&wall[src] : &nullwall;

    memset(&nullwall, 0, sizeof(nullwall));
    nullwall.yrepeat = 8;
    nullwall.extra = -1;

    if (reset_some)
    {
        dstwal->cstat = srcwal->cstat;
    }
    else
    {
        dstwal->cstat &= ~(4+8+256);
        dstwal->cstat |= (srcwal->cstat&(4+8+256));
    }
    dstwal->shade = srcwal->shade;
    dstwal->yrepeat = srcwal->yrepeat;
    fixrepeats(dst);  // xrepeat
    dstwal->picnum = srcwal->picnum;
    dstwal->overpicnum = srcwal->overpicnum;

    dstwal->pal = srcwal->pal;
    dstwal->xpanning = srcwal->xpanning;
    dstwal->ypanning = srcwal->ypanning;

    if (reset_some)
    {
        dstwal->nextwall = -1;
        dstwal->nextsector = -1;

        dstwal->lotag = 0; //srcwal->lotag;
        dstwal->hitag = 0; //srcwal->hitag;
        dstwal->extra = -1; //srcwal->extra;
#ifdef YAX_ENABLE
        yax_setnextwall(dst, YAX_CEILING, -1);
        yax_setnextwall(dst, YAX_FLOOR, -1);
#endif
    }
}

static void init_new_wall1(int16_t *suckwall_ret, int32_t mousxplc, int32_t mousyplc)
{
    int32_t i;

    Bmemset(&wall[newnumwalls], 0, sizeof(walltype));
    wall[newnumwalls].extra = -1;

    wall[newnumwalls].x = mousxplc;
    wall[newnumwalls].y = mousyplc;
    wall[newnumwalls].nextsector = -1;
    wall[newnumwalls].nextwall = -1;

    for (i=0; i<numwalls; i++)
    {
        YAX_SKIPWALL(i);
        if (wall[i].nextwall >= 0)
            YAX_SKIPWALL(wall[i].nextwall);

        if (wall[i].x == mousxplc && wall[i].y == mousyplc)
            *suckwall_ret = i;
    }

    wall[newnumwalls].point2 = newnumwalls+1;
    newnumwalls++;
}

// helpers for often needed ops:
static int32_t do_while_copyloop1(int16_t startwall, int16_t endwall,
                                  int16_t *danumwalls, int16_t lastpoint2)
{
    int32_t m = startwall;

    do
    {
        if (*danumwalls >= MAXWALLS + M32_FIXME_WALLS)
            return 1;

        Bmemcpy(&wall[*danumwalls], &wall[m], sizeof(walltype));
        wall[*danumwalls].point2 = *danumwalls+1;
        (*danumwalls)++;
        m = wall[m].point2;
    }
    while (m != endwall);

    if (lastpoint2 >= 0)
        wall[(*danumwalls)-1].point2 = lastpoint2;

    return 0;
}

static void updatesprite1(int16_t i)
{
    setsprite(i, &sprite[i].pos);

    if (sprite[i].sectnum>=0)
    {
        int32_t cz, fz;
        spriteoncfz(i, &cz, &fz);
        inpclamp(&sprite[i].z, cz, fz);
    }
}

#ifdef YAX_ENABLE
// highlighted OR grayed-out sectors:
static uint8_t hlorgraysectbitmap[(MAXSECTORS+7)>>3];
static int32_t ask_above_or_below(void);
#else
# define hlorgraysectbitmap hlsectorbitmap
#endif

// returns:
//  0: continue
// >0: newnumwalls
// <0: error
// ignore_ret and refsect_ret are for the 'auto-red-wall' feature
static int32_t trace_loop(int32_t j, uint8_t *visitedwall, int16_t *ignore_ret, int16_t *refsect_ret,
                          int16_t trace_loop_yaxcf)
{
    int16_t refsect, ignore;
    int32_t k, n, refwall;
#if 0
//def YAX_ENABLE
    int32_t yaxp = (ignore_ret==NULL);  // bleh
#else
    UNREFERENCED_PARAMETER(trace_loop_yaxcf);
#endif

    if (wall[j].nextwall>=0 || (visitedwall[j>>3]&pow2char[j&7]))
        return 0;

    n=2*MAXWALLS;  // simple inf loop check
    refwall = j;
    k = numwalls;

    ignore = 0;

    if (ignore_ret)
    {
        refsect = -1;
        updatesectorexclude(wall[j].x, wall[j].y, &refsect, hlorgraysectbitmap);
        if (refsect<0)
            return -1;
    }

    do
    {
        if (j!=refwall && visitedwall[j>>3]&pow2char[j&7])
            ignore = 1;
        visitedwall[j>>3] |= pow2char[j&7];

        if (ignore_ret)
        {
            if (inside(wall[j].x, wall[j].y, refsect) != 1)
                ignore = 1;
        }

        if (!ignore)
        {
            if (k>=MAXWALLS)
            {
                message("Wall limits exceeded while tracing outer loop.");
                return -2;
            }

            if (ignore_ret)  // auto-red wall feature
                onextwall[k] = onextwall[j];

            Bmemcpy(&wall[k], &wall[j], sizeof(walltype));
            wall[k].point2 = k+1;
// TODO: protect lotag/extra; see also hl-sector copying stuff
            wall[k].nextsector = wall[k].nextwall = wall[k].extra = -1;
#ifdef YAX_ENABLE
            if (trace_loop_yaxcf >= 0)
                yax_setnextwall(k, trace_loop_yaxcf, j);
#endif
            k++;
        }

        j = wall[j].point2;
        n--;

        while (wall[j].nextwall>=0 && n>0)
        {
#if 0
//def YAX_ENABLE
            if (yaxp)
            {
                int32_t ns = wall[j].nextsector;
                if ((hlsectorbitmap[ns>>3]&pow2char[ns&7])==0)
                    break;
            }
#endif
            j = wall[wall[j].nextwall].point2;
//            if (j!=refwall && (visitedwall[j>>3]&pow2char[j&7]))
//                ignore = 1;
//            visitedwall[j>>3] |= pow2char[j&7];
            n--;
        }
    }
    while (j!=refwall && n>0);

    if (j!=refwall)
    {
        message("internal error while tracing outer loop: didn't reach refwall");
        return -3;
    }

    if (ignore_ret)
    {
        *ignore_ret = ignore;
        if (refsect_ret)
            *refsect_ret = refsect;
    }

    return k;
}

// Backup drawn walls for carrying out other operations in the middle.
//  0: back up, set newnumwalls to -1
//  1: restore drawn walls and free mem
//  2: only free memory needed for backing up walls but don't restore walls
//     (use this if the map has been mangled too much for a safe restoration)
// Context that needs special treatment: suckwall, splitsect, splitstartwall
static int32_t backup_drawn_walls(int32_t restore)
{
    static uwalltype *tmpwall;

    // back up
    if (restore==0)
    {
        // ovh.bak_wallsdrawn should be 0 here

        if (newnumwalls != -1)
        {
            if (newnumwalls <= numwalls)  // shouldn't happen
                return 2;

            Xfree(tmpwall);
            tmpwall = (uwalltype *)Xmalloc((newnumwalls-numwalls) * sizeof(walltype));

            ovh.bak_wallsdrawn = newnumwalls-numwalls;

            Bmemcpy(tmpwall, &wall[numwalls], ovh.bak_wallsdrawn*sizeof(walltype));
            newnumwalls = -1;
        }

        return 0;
    }

    // restore/clear
    if (tmpwall)
    {
        if (restore==1)  // really restore
        {
            const int32_t nnumwalls = numwalls + ovh.bak_wallsdrawn;

            if (nnumwalls < MAXWALLS)  // else, silently discard drawn walls
            {
                int32_t i;

                Bmemcpy(&wall[numwalls], tmpwall, ovh.bak_wallsdrawn*sizeof(walltype));

                newnumwalls = nnumwalls;
                for (i=numwalls; i<newnumwalls; i++)
                    wall[i].point2 = i+1;
            }
        }

        DO_FREE_AND_NULL(tmpwall);

        ovh.bak_wallsdrawn = 0;
    }

    return 0;
}

// VARIOUS RESETTING FUNCTIONS
#define RESET_EDITOR_VARS() do { \
    sectorhighlightstat = -1; \
    newnumwalls = -1; \
    joinsector[0] = -1; \
    circlewall = -1; \
    circlepoints = 7; \
    } while (0)


void reset_highlightsector(void)
{
    Bmemset(hlsectorbitmap, 0, sizeof(hlsectorbitmap));
    update_highlightsector();
}

void reset_highlight(void)  // walls and sprites
{
    Bmemset(show2dwall, 0, sizeof(show2dwall));
    Bmemset(show2dsprite, 0, sizeof(show2dsprite));
    update_highlight();
}

#ifdef YAX_ENABLE
static int16_t collnumsects[2];
static int16_t collsectlist[2][MAXSECTORS];
static uint8_t collsectbitmap[2][(MAXSECTORS+7)>>3];

static void collect_sectors1(int16_t *sectlist, uint8_t *sectbitmap, int16_t *numsectptr,
                             int16_t startsec, int32_t alsoyaxnext, int32_t alsoonw)
{
    int32_t j, startwall, endwall, sectcnt;

    bfirst_search_init(sectlist, sectbitmap, numsectptr, MAXSECTORS, startsec);

    for (sectcnt=0; sectcnt<*numsectptr; sectcnt++)
    {
        for (WALLS_OF_SECTOR(sectlist[sectcnt], j))
        {
            if (wall[j].nextsector >= 0)
                bfirst_search_try(sectlist, sectbitmap, numsectptr, wall[j].nextsector);
            else if (alsoonw && onextwall[j]>=0)
                bfirst_search_try(sectlist, sectbitmap, numsectptr, sectorofwall(onextwall[j]));
        }

        if (alsoyaxnext)
        {
            int16_t bn[2], cf;
            yax_getbunches(sectlist[sectcnt], &bn[0], &bn[1]);
            for (cf=0; cf<2; cf++)
                if (bn[cf]>=0)
                {
                    for (SECTORS_OF_BUNCH(bn[cf], !cf, j))
                        bfirst_search_try(sectlist, sectbitmap, numsectptr, j);
                }
        }
    }
}


static int32_t sectors_components(int16_t hlsectcnt, const int16_t *hlsectors, int32_t alsoyaxnext, int32_t alsoonw);
static int32_t highlighted_sectors_components(int32_t alsoyaxnext, int32_t alsoonw)
{
    return sectors_components(highlightsectorcnt, highlightsector, alsoyaxnext, alsoonw);
}

// whether all highlighted sectors are in one (returns 1), two (2)
// or more (>2) connected components wrt the nextsector relation
// -1 means error
//  alsoyaxnext: also consider "yax-nextsector" relation
//  alsoonw: also consider "old-nextwall" relation (must be valid)
static int32_t sectors_components(int16_t hlsectcnt, const int16_t *hlsector, int32_t alsoyaxnext, int32_t alsoonw)
{
    int32_t j, k, tmp;

    if (hlsectcnt<1)
        return 0;

    collect_sectors1(collsectlist[0], collsectbitmap[0], &collnumsects[0],
                     hlsector[0], alsoyaxnext, alsoonw);

    for (k=1; k<hlsectcnt; k++)
    {
        j = hlsector[k];
        if ((collsectbitmap[0][j>>3]&pow2char[j&7])==0)
        {
            // sector j not collected --> more than 1 conn. comp.
            collect_sectors1(collsectlist[1], collsectbitmap[1], &collnumsects[1],
                             j, alsoyaxnext, alsoonw);
            break;
        }
    }

    if (k == hlsectcnt)
        return 1;

    for (k=0; k<hlsectcnt; k++)
    {
        j = hlsector[k];
        tmp = (((collsectbitmap[0][j>>3]&pow2char[j&7])!=0) + (((collsectbitmap[1][j>>3]&pow2char[j&7])!=0)<<1));

        if (tmp==3)
            return -1;  // components only weakly connected

        if (tmp==0)
            return 3;  // sector j not reached
    }

    return 2;
}

static int cmpgeomwal1(const void *w1, const void *w2)
{
    auto const wal1 = (uwallptr_t)&wall[B_UNBUF16(w1)];
    auto const wal2 = (uwallptr_t)&wall[B_UNBUF16(w2)];

    if (wal1->x == wal2->x)
        return wal1->y - wal2->y;

    return wal1->x - wal2->x;
}

static void sort_walls_geometrically(int16_t *wallist, int32_t nwalls)
{
    qsort(wallist, nwalls, sizeof(int16_t), &cmpgeomwal1);
}
#endif

void SetFirstWall(int32_t sectnum, int32_t wallnum, int32_t alsoynw)
{
#ifdef YAX_ENABLE
    int32_t i, j, k=0;
#endif
    const sectortype *sec = &sector[sectnum];

    if (sec->wallptr == wallnum)
    {
        message("Wall %d already first wall of sector %d", wallnum, sectnum);
        return;
    }

#ifdef YAX_ENABLE
    if (alsoynw)
    {
        // Also consider upper/lower TROR neighbor walls.
        int32_t startwall, endwall;
        int16_t cf;

        for (i=0; i<numwalls; i++)
            editwall[i>>3] &= ~pow2char[i&7];

        for (cf=0; cf<2; cf++)
        {
            int16_t bunchnum;
            int32_t tempsect=sectnum, tempwall=wallnum;

            while ((bunchnum = yax_getbunch(tempsect, cf)) >= 0 &&
                   (tempsect=yax_is121(bunchnum, cf)) >= 0)
            {
                tempwall = yax_getnextwall(tempwall, cf);
                if (tempwall < 0)
                    break;  // corrupt!
                editwall[tempwall>>3] |= 1<<(tempwall&7);
            }
        }

        for (i=0; i<numsectors; i++)
            for (WALLS_OF_SECTOR(i, j))
            {
                if (editwall[j>>3]&pow2char[j&7])
                {
                    setfirstwall(i, j);
                    k++;
                    break;
                }
            }
    }
    else
    {
        // Only consider aimed at wall <wallnum>.
        int16_t cb = yax_getbunch(sectnum, YAX_CEILING);
        int16_t fb = yax_getbunch(sectnum, YAX_FLOOR);

        if ((cb>=0 && (sec->ceilingstat&2)) || (fb >= 0 && (sec->floorstat&2)))
        {
            message("Extended ceilings/floors must not be sloped to set first wall");
            return;
        }
    }

    if (k > 0)
        message("Set first walls (sector[].wallptr) for %d sectors", k+1);

    if (k == 0)
#endif
        message("This wall now sector %d's first wall (sector[].wallptr)", sectnum);

    setfirstwall(sectnum, wallnum);

    mkonwinvalid_keeptempsect();

    asksave = 1;
}

void handlesecthighlight1(int32_t i, int32_t sub, int32_t nograycheck)
{
    int32_t j;

    if (sub)
    {
        hlsectorbitmap[i>>3] &= ~pow2char[i&7];
        for (j=sector[i].wallptr; j<sector[i].wallptr+sector[i].wallnum; j++)
        {
            if (wall[j].nextwall >= 0)
                checksectorpointer(wall[j].nextwall,wall[j].nextsector);
            checksectorpointer(j, i);
        }
    }
    else
    {
        if (nograycheck || (graysectbitmap[i>>3]&pow2char[i&7])==0)
            hlsectorbitmap[i>>3] |= pow2char[i&7];
    }
}

#ifdef YAX_ENABLE
// 1: good, 0: bad
static int32_t hl_all_bunch_sectors_p()
{
    uint8_t *const havebunch = visited;
    int16_t cf, cb, fb;
    int32_t i, j;

    if (numyaxbunches > 0)
    {
        Bmemset(havebunch, 0, (numyaxbunches+7)>>3);
        for (i=0; i<highlightsectorcnt; i++)
        {
            yax_getbunches(highlightsector[i], &cb, &fb);
            if (cb>=0)
                havebunch[cb>>3] |= pow2char[cb&7];
            if (fb>=0)
                havebunch[fb>>3] |= pow2char[fb&7];
        }

        for (i=0; i<numyaxbunches; i++)
        {
            if ((havebunch[i>>3] & pow2char[i&7])==0)
                continue;

            for (cf=0; cf<2; cf++)
                for (SECTORS_OF_BUNCH(i,cf, j))
                    if ((hlsectorbitmap[j>>3]&pow2char[j&7])==0)
                        return 0;
        }
    }

    return 1;
}
#endif

static int32_t find_nextwall(int32_t sectnum, int32_t sectnum2)
{
    int32_t j, startwall, endwall;

    if (sectnum<0 || sectnum2<0)
        return -1;

    for (WALLS_OF_SECTOR(sectnum, j))
        if (wall[j].nextsector == sectnum2)
            return j;

    return -1;
}

static int32_t bakframe_fillandfade(char **origframeptr, int32_t sectnum, const char *querystr)
{
    if (!*origframeptr)
    {
        *origframeptr = (char *)Xmalloc(xdim*ydim);

        videoBeginDrawing();
        Bmemcpy(*origframeptr, (char *)frameplace, xdim*ydim);
        videoEndDrawing();
    }
    else
    {
        videoBeginDrawing();
        Bmemcpy((char *)frameplace, *origframeptr, xdim*ydim);
        videoEndDrawing();
    }

    fillsector_notrans(sectnum, editorcolors[9]);
    fade_editor_screen(editorcolors[9]);

    return ask_if_sure(querystr, 0);
}

#ifdef YAX_ENABLE
static void M32_MarkPointInsertion(int32_t thewall)
{
    int32_t i, tmpcf;
    int32_t nextw = wall[thewall].nextwall;

    // round 1
    for (YAX_ITER_WALLS(thewall, i, tmpcf))
        editwall[i>>3] |= 1<<(i&7);
    if (nextw >= 0)
        for (YAX_ITER_WALLS(nextw, i, tmpcf))
                editwall[i>>3] |= 1<<(i&7);
    // round 2 (enough?)
    for (YAX_ITER_WALLS(thewall, i, tmpcf))
        if (wall[i].nextwall >= 0 && (editwall[wall[i].nextwall>>3]&pow2char[wall[i].nextwall&7])==0)
            editwall[wall[i].nextwall>>3] |= 1<<(wall[i].nextwall&7);
    if (nextw >= 0)
        for (YAX_ITER_WALLS(nextw, i, tmpcf))
            if (wall[i].nextwall >= 0 && (editwall[wall[i].nextwall>>3]&pow2char[wall[i].nextwall&7])==0)
                editwall[wall[i].nextwall>>3] |= 1<<(wall[i].nextwall&7);
}
#endif

// High-level insert point, handles TROR constrained walls too
//  onewnumwalls: old numwalls + drawn walls.
// <mapwallnum>: see insertpoint()
// Returns:
//  0 if wall limit would be reached.
//  1 if inserted point on a plain white or 2 points on a plain red wall.
//  N >= 2 if inserted N points on TROR-constrained wall.
//  N|(EXPECTED<<16) if inserted N points but EXPECTED walls were expected.
static int32_t M32_InsertPoint(int32_t thewall, int32_t dax, int32_t day, int16_t onewnumwalls, int32_t *mapwallnum)
{
#ifdef YAX_ENABLE
    int32_t nextw = wall[thewall].nextwall;
    int32_t i, j, k, m;

    if (yax_islockedwall(thewall) || (nextw>=0 && yax_islockedwall(nextw)))
    {
        // yax'ed wall -- first find out which walls are affected
        for (i=0; i<numwalls; i++)
            editwall[i>>3] &= ~pow2char[i&7];

        M32_MarkPointInsertion(thewall);

        for (i=0; i < numwalls; i++)
            if (editwall[i>>3]&pow2char[i&7])
                M32_MarkPointInsertion(i);

        j = 0;
        for (i=0; i<numwalls; i++)
            j += !!(editwall[i>>3]&pow2char[i&7]);
        if (max(numwalls,onewnumwalls)+j > MAXWALLS)
        {
            return 0;  // no points inserted, would exceed limits
        }

        // the actual insertion!
        m = 0;
        for (i=0; i<numwalls /* rises with ins. */; i++)
        {
            if (editwall[i>>3]&pow2char[i&7])
                if (wall[i].nextwall<0 || i<wall[i].nextwall) // || !(NEXTWALL(i).cstat&(1<<14)) ??
                {
                    m += insertpoint(i, dax,day, mapwallnum);
                }
        }

        for (i=0; i<numwalls; i++)
        {
            if (editwall[i>>3]&pow2char[i&7])
            {
                editwall[i>>3] &= ~pow2char[i&7];
                k = yax_getnextwall(i+1, YAX_CEILING);
                if (k >= 0)
                    yax_setnextwall(i+1, YAX_CEILING, k+1);
                k = yax_getnextwall(i+1, YAX_FLOOR);
                if (k >= 0)
                    yax_setnextwall(i+1, YAX_FLOOR, k+1);
            }
        }

        if (m==j)
            return m;
        else
            return m|(j<<16);
    }
    else
#endif
    {
        insertpoint(thewall, dax,day, mapwallnum);
        return 1;
    }
}


// based on lineintersect in engine.c, but lines are considered as infinitely
// extending
void inflineintersect(int32_t x1, int32_t y1, int32_t x2, int32_t y2,
                      int32_t x3, int32_t y3, int32_t x4, int32_t y4,
                      int32_t *intx, int32_t *inty, int32_t *sign12, int32_t *sign34)
{
    //p1 to p2 is a line segment

    int64_t const x21 = x2-x1;
    int64_t const x34 = x3-x4;
    int64_t const y21 = y2-y1;
    int64_t const y34 = y3-y4;
    int64_t const bot = x21*y34 - y21*x34;

    if (EDUKE32_PREDICT_FALSE(bot == 0))
    {
        *sign12 = *sign34 = 0;
        return;
    }

    int64_t const x31 = x3-x1;
    int64_t const y31 = y3-y1;

    int64_t const topt = x31*y34 - y31*x34;
    int64_t const topu = x21*y31 - y21*x31;

    int64_t const t = tabledivide64_noinline(topt*(1<<24), bot);

    *intx = x1 + ((x21*t)>>24);
    *inty = y1 + ((y21*t)>>24);

    *sign12 = topt < 0 ? -1 : 1;
    *sign34 = topu < 0 ? -1 : 1;
}

static int32_t lineintersect2v(const vec2_t *p1, const vec2_t *p2,  // line segment 1
                               const vec2_t *q1, const vec2_t *q2,  // line segment 2
                               vec2_t *pint)
{
    int32_t intz;
    return lintersect(p1->x, p1->y, 0, p2->x, p2->y, 0,
                         q1->x, q1->y, q2->x, q2->y,
                         &pint->x, &pint->y, &intz);
}

static int32_t vec2eq(const vec2_t *v1, const vec2_t *v2)
{
    return (v1->x==v2->x && v1->y==v2->y);
}

#ifdef YAX_ENABLE
// After auto-creating inner sector <ns> in existing sector <os>, we need to
// see if some sprites contained in <os> need to change their sector.
static void CorrectSpriteSectnums(int32_t os, int32_t ns)
{
    int32_t i, ni;

    for (SPRITES_OF_SECT_SAFE(os, i, ni))
    {
        if (inside(sprite[i].x, sprite[i].y, ns)==1)
            changespritesect(i, ns);
    }
}
#endif

// precondition: [numwalls, newnumwalls-1] form a new loop (may be of wrong orientation)
// ret_ofirstwallofs: if != NULL, *ret_ofirstwallofs will contain the offset of the old
//                    first wall from the new first wall of the sector k, and the automatic
//                    restoring of the old first wll will not be carried out
// returns:
//  -1, -2: errors
//   0,  1: OK, 1 means it was an extended sector and an inner loop has been added automatically
static int32_t AddLoopToSector(int32_t k, int32_t *ret_ofirstwallofs)
{
    int32_t extendedSector=0, firstwall, i, j;
#ifdef YAX_ENABLE
    int16_t cbunch, fbunch;
    int32_t newnumwalls2;

    yax_getbunches(k, &cbunch, &fbunch);
    extendedSector = (cbunch>=0 || fbunch>=0);
#endif
    j = newnumwalls-numwalls;
#ifdef YAX_ENABLE
    newnumwalls2 = newnumwalls + j;

    if (extendedSector)
    {
        if ((cbunch>=0 && (sector[k].ceilingstat&2))
            || (fbunch>=0 && (sector[k].floorstat&2)))
        {
            printmessage16("Sloped extended sectors cannot be subdivided.");
            newnumwalls--;
            return -1;
        }

        if (newnumwalls + j > MAXWALLS || numsectors+1 > MAXSECTORS)
        {
            message("Automatically adding inner sector to new extended sector would exceed limits!");
            newnumwalls--;
            return -2;
        }
    }
#endif
    if (clockdir(numwalls) == CLOCKDIR_CW)
        flipwalls(numwalls,newnumwalls);

    sector[k].wallnum += j;
    for (i=k+1; i<numsectors; i++)
        sector[i].wallptr += j;
    firstwall = sector[k].wallptr;

    for (i=0; i<numwalls; i++)
    {
        if (wall[i].nextwall >= firstwall)
            wall[i].nextwall += j;
        if (wall[i].point2 >= firstwall)
            wall[i].point2 += j;
    }
#ifdef YAX_ENABLE
    yax_tweakwalls(firstwall, j);
#endif

    Bmemmove(&wall[firstwall+j], &wall[firstwall], (newnumwalls-firstwall)*sizeof(walltype));
    // add new loop to beginning of sector
    Bmemmove(&wall[firstwall], &wall[newnumwalls], j*sizeof(walltype));

    for (i=firstwall; i<firstwall+j; i++)
    {
        wall[i].point2 += (firstwall-numwalls);

        copy_some_wall_members(i, firstwall+j, 1);
        wall[i].cstat &= ~(1+16+32+64);
    }

    numwalls = newnumwalls;
    newnumwalls = -1;
#ifdef YAX_ENABLE
    if (extendedSector)
    {
        newnumwalls = whitelinescan(k, firstwall);
        if (newnumwalls != newnumwalls2)
            message("AddLoopToSector: newnumwalls != newnumwalls2!!! WTF?");
        for (i=numwalls; i<newnumwalls; i++)
        {
            NEXTWALL(i).nextwall = i;
            NEXTWALL(i).nextsector = numsectors;
        }

        yax_setbunches(numsectors, cbunch, fbunch);

        numwalls = newnumwalls;
        newnumwalls = -1;
        numsectors++;

        CorrectSpriteSectnums(k, numsectors-1);
    }
#endif
    if (ret_ofirstwallofs)
        *ret_ofirstwallofs = j;
    else
        setfirstwall(k, firstwall+j);  // restore old first wall

    return extendedSector;
}

int32_t select_sprite_tag(int32_t spritenum)
{
    int32_t lt = taglab_linktags(1, spritenum);
    spritetype *spr = &sprite[spritenum];

    if (lt==0)
        return INT32_MIN;

    if (lt&1)
        return spr->lotag;
    if (lt&2)
        return spr->hitag;
    if (lt&4)
        return spr->extra;
    if (lt&8)
        return spr->xvel;
    if (lt&16)
        return spr->yvel;
    if (lt&32)
        return spr->zvel;
    if (lt&64)
        return spr->extra;

    return INT32_MIN;
}

static void drawlinebetween(const vec3_t *v1, const vec3_t *v2, int32_t col, uint32_t pat)
{
    // based on m32exec.c/drawline*
    const int32_t xofs=halfxdim16, yofs=midydim16;
    const uint32_t opat=drawlinepat;

    int32_t x1, x2, y1, y2;

    editorGet2dScreenCoordinates(&x1,&y1, v1->x-pos.x,v1->y-pos.y, zoom);
    editorGet2dScreenCoordinates(&x2,&y2, v2->x-pos.x,v2->y-pos.y, zoom);

    if (m32_sideview)
    {
        y1 += getscreenvdisp(v1->z-pos.z,zoom);
        y2 += getscreenvdisp(v2->z-pos.z,zoom);
    }

    drawlinepat = pat;
    editorDraw2dLine(xofs+x1,yofs+y1, xofs+x2,yofs+y2, col);
    drawlinepat = opat;
}

// world -> screen coords for overhead mode
void ovhscrcoords(int32_t x, int32_t y, int32_t *scrx, int32_t *scry)
{
    *scrx = halfxdim16 + mulscale14(x-pos.x, zoom);
    *scry = midydim16 + mulscale14(y-pos.y, zoom);
}

static void draw_cross(int32_t centerx, int32_t centery, int32_t radius, int32_t col)
{
    int32_t dax, day;
    ovhscrcoords(centerx, centery, &dax, &day);
    drawline16base(dax, day, -radius,-radius, +radius,+radius, col);
    drawline16base(dax, day, -radius,+radius, +radius,-radius, col);
}

static void draw_square(int32_t dax, int32_t day, int32_t ps, int32_t col)
{
    ovhscrcoords(dax, day, &dax, &day);
    drawline16base(dax, day, -ps,-ps, +ps,-ps, col);
    drawline16base(dax, day, +ps,-ps, +ps,+ps, col);
    drawline16base(dax, day, +ps,+ps, -ps,+ps, col);
    drawline16base(dax, day, -ps,+ps, -ps,-ps, col);
}

//// Interactive Scaling
static struct {
    int8_t active, rotatep;
    vec2_t piv;  // pivot point
    int32_t dragx, dragy;  // dragged point
    int32_t xsc, ysc, ang;
} isc;

static void isc_transform(int32_t *x, int32_t *y)
{
    if (!isc.rotatep)
    {
        *x = isc.piv.x + mulscale16(*x-isc.piv.x, isc.xsc);
        *y = isc.piv.y + mulscale16(*y-isc.piv.y, isc.ysc);
    }
    else
    {
        vec2_t v = { *x, *y };
        rotatepoint(isc.piv, v, isc.ang, &v);
        *x = v.x;
        *y = v.y;
    }
}

static void drawspritelabel(int i)
{
    // XXX: oob 'i' may happen, such as passing pointhighlight-16384 when
    // pointhighlight == -1.
    if ((unsigned)i >= MAXSPRITES)
        return;

    const char *dabuffer = CallExtGetSpriteCaption(i);

    if (!dabuffer[0])
        return;

    // KEEPINSYNC drawscreen_drawsprite()
    uspriteptr_t s = (uspriteptr_t)&sprite[i];
    uint8_t const spritecol = spritecol2d[s->picnum][(s->cstat&1)];
    int col = spritecol ? editorcolors[spritecol] : editorGet2dSpriteColor(i);
    int const blocking = s->cstat & 1;
    int bordercol = blocking ? editorcolors[5] : col;

    // group selection
    if (show2dsprite[i>>3]&pow2char[i&7])
    {
        bordercol = editorcolors[14];
        col = bordercol - (M32_THROB>>1);
    }
    else if (i == pointhighlight - 16384)
    {
        if (spritecol >= 8 && spritecol <= 15)
            col -= M32_THROB>>1;
        else col += M32_THROB>>2;

        if (bordercol > col && !blocking)
            bordercol = col;
    }

    else if (s->sectnum < 0)
        col = bordercol = editorcolors[4];  // red

    drawsmallabel(dabuffer, editorcolors[0], col, bordercol, s->x, s->y, s->z);
}

#define EDITING_MAP_P() (newnumwalls>=0 || joinsector[0]>=0 || circlewall>=0 || (bstatus&1) || isc.active)

#define HLMEMBERX(Hl, Member) (*(((Hl)&16384) ? &sprite[(Hl)&16383].Member : &wall[Hl].Member))
#define HLMEMBER(Hlidx, Member) HLMEMBERX(highlight[Hlidx], Member)

static void maybedeletewalls(int32_t dax, int32_t day)
{
    int       numdelpoints   = 0;
    int const havedrawnwalls = (newnumwalls != -1);
    int       restorestat    = 1;

    // attempt to delete some points
    for (int runi=0; runi<3; runi++)  // check, tweak, carry out
        for (int i=numwalls-1; i>=0; i--)
        {
            if (runi==0)
                editwall[i>>3] &= ~pow2char[i&7];;

            if (wall[i].x == POINT2(i).x && wall[i].y == POINT2(i).y)
            {
                if (havedrawnwalls)
                {
                    if (i==ovh.suckwall || (ovh.split && i==ovh.splitstartwall))
                    {
                        // if we're about to delete a wall that participates
                        // in splitting, discard the already drawn walls
                        restorestat = 2;
                    }
                    else if (runi == 1)
                    {
                        // correct drawn wall anchors
                        if (ovh.suckwall > i)
                            ovh.suckwall--;
                        if (ovh.split && ovh.splitstartwall > i)
                            ovh.splitstartwall--;
                    }
                }

                if (runi == 0)
                {
                    int32_t sectnum = sectorofwall(i);
                    if (sector[sectnum].wallnum <= 3)
                    {
                        message("Deleting wall %d would leave sector %d with %d walls.",
                            i, sectnum, sector[sectnum].wallnum-1);
                        goto end_after_dragging;
                    }

                    sectnum = wall[i].nextsector;
                    if (sectnum >= 0 && sector[sectnum].wallnum <= 3)
                    {
                        message("Deleting wall %d would leave sector %d with %d walls.",
                            i, sectnum, sector[sectnum].wallnum-1);
                        goto end_after_dragging;
                    }
                }
                else
                {
                    deletepoint(i, runi);
                    if (runi==2)
                        numdelpoints++;
                }
            }
        }

    if (numdelpoints)
    {
        if (numdelpoints > 1)
            message("Deleted %d points%s", numdelpoints,
            (havedrawnwalls && restorestat==2) ? " and cleared drawn walls" : "");
        else
            printmessage16("Point deleted%s", (havedrawnwalls && restorestat==2) ?
                ", cleared drawn walls" : "");
        asksave = 1;
    }
    else
    {
        for (int i=0; i<numwalls; i++)     //make new red lines?
        {
            YAX_SKIPWALL(i);

            if ((wall[i].x == dax && wall[i].y == day)
                || (POINT2(i).x == dax && POINT2(i).y == day))
            {
                checksectorpointer(i, sectorofwall(i));
                //                    fixrepeats(i);
                asksave = 1;
            }
        }
    }
#ifdef YAX_ENABLE
    yax_update(0);
    yax_updategrays(pos.z);
#endif
end_after_dragging:
    backup_drawn_walls(restorestat);
}

static void deletewall(int w)
{
    if ((w & 0xc000) != 16384)
    {
        if (sector[sectorofwall(w)].wallnum > 3 && (wall[w].nextwall == -1 || sector[sectorofwall(wall[w].nextwall)].wallnum > 3))
        {
            dragpoint(w, POINT2(w).x, POINT2(w).y, 0);
            maybedeletewalls(wall[w].x, wall[w].y);
        }
/*
        else
        {
            deletesector(sectorofwall(w));
            mkonwinvalid();
            printmessage16("Sector deleted.");
        }
*/

    }
}

void overheadeditor(void)
{
    char buffer[80];
    const char *dabuffer;
    int32_t i, j, k, m=0, mousxplc, mousyplc, firstx=0, firsty=0, oposz, col;
    int32_t numwalls_bak;
    int32_t startwall=0, endwall, dax, day, x1, y1, x2, y2, x3, y3; //, x4, y4;
    int16_t bad, joinsector[2];
    int32_t bstatus, mousewaitmask=0;
    int16_t circlepoints;
    int32_t sectorhighlightx=0, sectorhighlighty=0;
    int16_t cursectorhighlight, sectorhighlightstat;
    int32_t prefixarg = 0, tsign;
    int32_t resetsynctics = 0, lasttick=timerGetTicks(), waitdelay=(int32_t) totalclock, lastdraw=timerGetTicks();
    int32_t olen[2] = {0, 0}, dragwall[2] = {-1, -1};
    int16_t linehighlight2 = -1;
    vec2_t highlight1 = { 0, 0 }, highlight2 = { 0, 0 };

    ovh.suckwall = -1;
    ovh.split = 0;
    ovh.splitsect = -1;
    ovh.splitstartwall = -1;

    videoSet2dMode(xdim2d,ydim2d);
    xdim2d = xdim;
    ydim2d = ydim;

    osearchx = searchx;
    osearchy = searchy;

    searchx = clamp(scale(searchx,xdim2d,xdimgame), 8, xdim2d-8-1);
    searchy = clamp(scale(searchy,ydim2d-STATUS2DSIZ2,ydimgame), 8, ydim2d-STATUS2DSIZ-8-1);
    oposz = pos.z;

    yax_updategrays(pos.z);

    videoBeginDrawing(); //{{{
    CLEARLINES2D(0, ydim, 0);
    videoEndDrawing(); //}}}

    ydim16 = ydim-STATUS2DSIZ2;

    cursectorhighlight = -1;
    lastpm16time = -1;

    update_highlightsector();
    ovh_whiteoutgrab(0);

    highlightcnt = -1;
    Bmemset(show2dwall, 0, sizeof(show2dwall));  //Clear all highlights
    Bmemset(show2dsprite, 0, sizeof(show2dsprite));

    RESET_EDITOR_VARS();
    bstatus = 0;

    while ((keystatus[buildkeys[BK_MODE2D_3D]]>>1) == 0)
    {
        int32_t mousx = 0, mousy = 0;

        if (zoom < ztarget)
        {
            if ((ztarget - zoom) >> 3)
                zoom += synctics * ((ztarget - zoom) >> 3);
            else zoom++;
            zoom = min(zoom, ztarget);
        }
        else if (zoom > ztarget)
        {
            if ((zoom - ztarget) >> 3)
                zoom -= synctics * ((zoom - ztarget) >> 3);
            else zoom--;
            zoom = max(zoom, ztarget);
        }

        if (!((vel|angvel|svel) || m32_is2d3dmode() || ztarget != zoom//DOWN_BK(MOVEFORWARD) || DOWN_BK(MOVEBACKWARD) || DOWN_BK(TURNLEFT) || DOWN_BK(TURNRIGHT)
                || DOWN_BK(MOVEUP) || DOWN_BK(MOVEDOWN) || keystatus[sc_Q] || keystatus[sc_W]
                || keystatus[sc_kpad_8] || keystatus[sc_kpad_4] || keystatus[sc_kpad_6] || keystatus[sc_kpad_2]  // keypad keys
                || bstatus || OSD_IsMoving()))
        {
            if (totalclock > waitdelay)
            {
                uint32_t ms = 50;// (highlightsectorcnt>0) ? 75 : 200;
                // wait for event, timeout after 200 ms - (last loop time)
                idle_waitevent_timeout(ms - min(timerGetTicks()-lasttick, ms));
                // have synctics reset to 0 after we've slept to avoid zooming out to the max instantly
                resetsynctics = 1;
            }
        }
        else waitdelay = (int32_t) totalclock + 6; // should be 50 ms

        lasttick = timerGetTicks();

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

        if (resetsynctics)
        {
            resetsynctics = 0;
            lockclock = (int32_t) totalclock;
            synctics = 0;
        }

        OSD_DispatchQueued();

        if (totalclock < 120*3)
            printmessage16("Uses BUILD technology by Ken Silverman.");
        else if (totalclock < 120*6)
        {
            printmessage16("Press F1 for help.  This is a test release; always keep backups of your maps.");
            //        printext16(8L,ydim-STATUS2DSIZ+32L,editorcolors[9],-1,kensig,0);
        }

        if (!m32_is2d3dmode())
        {
            oldmousebstatus = bstatus;
            mouseGetValues(&mousx, &mousy, &bstatus);

            {
                int32_t bs = bstatus;
                bstatus &= ~mousewaitmask;
                mousewaitmask &= bs;
            }

            mousx = (mousx<<16)+mousexsurp;
            mousy = (mousy<<16)+mouseysurp;
            {
                ldiv_t ld;
                ld = ldiv(mousx, 1<<16); mousx = ld.quot; mousexsurp = ld.rem;
                ld = ldiv(mousy, 1<<16); mousy = ld.quot; mouseysurp = ld.rem;
            }
            searchx += mousx;
            searchy += mousy;

            inpclamp(&searchx, 8, xdim-8-1);
            inpclamp(&searchy, 8, ydim-8-1);

            mainloop_move();

            getpoint(searchx, searchy, &mousxplc, &mousyplc);
            linehighlight = getlinehighlight(mousxplc, mousyplc, linehighlight, 0);
            linehighlight2 = getlinehighlight(mousxplc, mousyplc, linehighlight, 1);

            if (!m32_sideview)
                updatesector(mousxplc, mousyplc, &sectorhighlight);
            else
                sectorhighlight = -1;
        }

        if ((unsigned)newnumwalls < MAXWALLS && newnumwalls >= numwalls)
        {
            // if we're in the process of drawing a wall, set the end point's coordinates
            dax = mousxplc;
            day = mousyplc;
            adjustmark(&dax,&day,numwalls+!ovh.split);
            wall[newnumwalls].x = dax;
            wall[newnumwalls].y = day;
        }

        ydim16 = ydim;// - STATUS2DSIZ2;
        midydim16 = ydim>>1;

        numwalls_bak = numwalls;
        numwalls = newnumwalls;
        if (numwalls < 0)
            numwalls = numwalls_bak;

        if ((timerGetTicks() - lastdraw) >= 5 || (vel|angvel|svel) || DOWN_BK(MOVEUP) || DOWN_BK(MOVEDOWN)
                || mousx || mousy || bstatus || keystatus[sc_Q] || keystatus[sc_W]
                || newnumwalls>=0 || OSD_IsMoving())
        {
            lastdraw = timerGetTicks();

            clear2dscreen();

            editorSetup2dSideView();

            VM_OnEvent(EVENT_PREDRAW2DSCREEN, -1);

            if (graphicsmode && (!m32_sideview || m32_sideelev == 512))
            {
                Bmemset(show2dsector, 0, sizeof(show2dsector));
                for (i=0; i<numsectors; i++)
                {
                    YAX_SKIPSECTOR(i);
                    show2dsector[i>>3] |= pow2char[i&7];
                }

                videoSetViewableArea(0, 0, xdim-1, ydim16-1);

                if (graphicsmode == 2)
                    totalclocklock = totalclock;

                renderDrawMapView(pos.x, pos.y, zoom, m32_sideview ? (3584 - m32_sideang) & 2047: 1536);
            }

            editorDraw2dGrid(pos.x,pos.y,pos.z,cursectnum,ang,zoom,grid);
            CallExtPreCheckKeys();
            editorDraw2dScreen(&pos,cursectnum,ang,zoom,grid);

            // Draw brown arrow (start)
            editorGet2dScreenCoordinates(&x2, &y2, startpos.x-pos.x,startpos.y-pos.y, zoom);
            if (m32_sideview)
                y2 += getscreenvdisp(startpos.z-pos.z, zoom);

            int32_t cx = halfxdim16+x2;
            int32_t cy = midydim16+y2;

            videoBeginDrawing();        //{{{  LOCK_FRAME_1

            if ((cx >= 2 && cx <= xdim-3) && (cy >= 2 && cy <= ydim16-3))
            {
                int16_t angofs = m32_sideview ? m32_sideang : 0;
                x1 = mulscale11(sintable[(startang+angofs+2560)&2047],zoom) / 768;
                y1 = mulscale11(sintable[(startang+angofs+2048)&2047],zoom) / 768;
                i = scalescreeny(x1);
                j = scalescreeny(y1);
                drawline16base(cx,cy, x1,j, -x1,-j, editorcolors[6]);
                drawline16base(cx,cy, x1,j, +y1,-i, editorcolors[6]);
                drawline16base(cx,cy, x1,j, -y1,+i, editorcolors[6]);
            }

            if (keystatus[sc_LeftShift] && (pointhighlight&16384) && highlightcnt<=0)  // LShift
            {
                // draw lines to linking sprites
                const int32_t refspritenum = pointhighlight&16383;
                const int32_t reftag = select_sprite_tag(refspritenum);

                if (reftag != INT32_MIN)
                {
                    for (i=0; i<numsectors; i++)
                        for (SPRITES_OF_SECT(i, j))
                            if (reftag==select_sprite_tag(j))
                                drawlinebetween(&sprite[refspritenum].pos, &sprite[j].pos, editorcolors[12], 0x33333333);
                }
            }

            if (showtags)
            {
                if (zoom >= 768)
                {
                    for (i=0; i<numsectors; i++)
                    {
                        int16_t secshort = i;

                        YAX_SKIPSECTOR(i);

                        dabuffer = CallExtGetSectorCaption(i);
                        if (dabuffer[0] == 0)
                            continue;

                        get_sectors_center(&secshort, 1, &dax, &day);

                        drawsmallabel(dabuffer, editorcolors[0], editorcolors[7], editorcolors[7] - 3, dax, day, getflorzofslope(i,dax,day));
                    }
                }

                x3 = pos.x + divscale14(-halfxdim16,zoom);
                y3 = pos.y + divscale14(-(midydim16-4),zoom);
//                x4 = pos.x + divscale14(halfxdim16,zoom);
//                y4 = pos.y + divscale14(ydim16-(midydim16-4),zoom);

                if (newnumwalls >= 0)
                {
                    for (i=newnumwalls; i>=numwalls_bak; i--)
                        editwall[i>>3] |= 1<<(i&7);;
                }

                i = numwalls-1;
                j = numsectors-1;  // might be -1 if empty map!
                if (newnumwalls >= 0)
                    i = newnumwalls-1;
                for (; i>=0; i--)
                {
                    walltype const * const wal = &wall[i];

                    if (j>=0 && sector[j].wallptr > i)
                        j--;

                    if (zoom < 768 && !(editwall[i>>3]&pow2char[i&7]))
                        continue;

                    YAX_SKIPWALL(i);

                    //Get average point of wall
//                    if ((dax > x3) && (dax < x4) && (day > y3) && (day < y4))
                    {
                        dabuffer = CallExtGetWallCaption(i);
                        if (dabuffer[0] == 0)
                            continue;

                        dax = (wal->x+wall[wal->point2].x)>>1;
                        day = (wal->y+wall[wal->point2].y)>>1;
                        drawsmallabel(dabuffer, editorcolors[0], editorcolors[31], editorcolors[31] - 3, dax, day, (i >= numwalls || j<0) ? 0 : getflorzofslope(j, dax,day));
                    }
                }

                if (zoom >= 768)
                {
                    int32_t alwaysshowgray = get_alwaysshowgray();

                    for (i=0, k=0; (m32_sideview && k<m32_swcnt) || (!m32_sideview && i<MAXSPRITES); i++, k++)
                    {
                        if (m32_sideview)
                        {
                            i = m32_wallsprite[k];
                            if (i<MAXWALLS)
                                continue;
                            i = i-MAXWALLS;
                        }
                        else
                            if (sprite[i].statnum == MAXSTATUS)
                                continue;

                        if ((!m32_sideview || !alwaysshowgray) && sprite[i].sectnum >= 0)
                            YAX_SKIPSECTOR(sprite[i].sectnum);

                        drawspritelabel(i);
                    }

                    if (pointhighlight & 16384)
                        drawspritelabel(pointhighlight - 16384);
                }
            }

            // stick this event right between begin- end enddrawing()...
            // also after the above label stuff so users can redefine them
            VM_OnEvent(EVENT_DRAW2DSCREEN, -1);

            printcoords16(pos.x,pos.y,ang);

            numwalls = numwalls_bak;

            if (highlightsectorcnt >= 0)
            {
                for (i=0; i<numsectors; i++)
                    if (hlsectorbitmap[i>>3]&pow2char[i&7])
                        fillsector(i, -1);
            }

            if (keystatus[sc_LeftShift])  // LShift
            {
                if (!m32_is2d3dmode() && (m32_sideview || highlightcnt <= 0))
                {
                    drawlinepat = 0x00ff00ff;
                    editorDraw2dLine(searchx,0, searchx,ydim2d-1, editorcolors[15]);
                    editorDraw2dLine(0,searchy, xdim2d-1,searchy, editorcolors[15]);
                    drawlinepat = 0xffffffff;

                    _printmessage16("(%d,%d)",mousxplc,mousyplc);
                }
                else
                {
                    // do interactive scaling
                    if (!isc.active)
                    {
                        if (pointhighlight >= 0 && (bstatus&3))
                        {
                            // initialize by finding pivot point
                            int32_t minx=INT32_MAX, miny=INT32_MAX;
                            int32_t maxx=INT32_MIN, maxy=INT32_MIN;

                            isc.rotatep = ((bstatus&3)==2);
                            bstatus &= ~3;

                            for (i=0; i<highlightcnt; i++)
                            {
                                minx = min(minx, HLMEMBER(i, x));
                                miny = min(miny, HLMEMBER(i, y));
                                maxx = max(maxx, HLMEMBER(i, x));
                                maxy = max(maxy, HLMEMBER(i, y));
                            }

                            isc.piv.x = (minx+maxx)/2;
                            isc.piv.y = (miny+maxy)/2;

                            isc.dragx = HLMEMBERX(pointhighlight, x);
                            isc.dragy = HLMEMBERX(pointhighlight, y);

                            isc.xsc = isc.ysc = 1<<16;
                            isc.ang = 0;

                            isc.active = 1;
                        }
                    }
                    else
                    {
                        if (bstatus&3)
                        {
                            // drag/rotate the reference point
                            const int32_t pivx=isc.piv.x, pivy=isc.piv.y;
                            const int32_t dragx=isc.dragx, dragy=isc.dragy;
                            int32_t mxplc=mousxplc, myplc=mousyplc, xsc=1<<16, ysc=1<<16;

                            const int32_t dx=dragx-pivx, dy=dragy-pivy;
                            int32_t mdx, mdy;

                            bstatus &= ~3;

                            draw_cross(pivx, pivy, 3, editorcolors[14]);

                            adjustmark(&mxplc, &myplc, numwalls);
                            mdx = mxplc-pivx;
                            mdy = myplc-pivy;

                            if (!isc.rotatep)
                            {
                                if (mdx != 0 && dx != 0 && klabs(dx) >= 8)
                                    xsc = min(klabs(divscale16(mdx, dx)), 1<<18);
                                if (mdy != 0 && dy != 0 && klabs(dy) >= 8)
                                    ysc = min(klabs(divscale16(mdy, dy)), 1<<18);

                                if (eitherCTRL)
                                    xsc = ysc = max(xsc, ysc);

                                isc.xsc = xsc;
                                isc.ysc = ysc;

                                printmessage16("scale x=%.3f y=%.3f", (double)xsc/65536, (double)ysc/65536);
                            }
                            else
                            {
                                isc.ang = getangle(mdx, mdy) - getangle(dx, dy);
                                printmessage16("rotate ang %d", isc.ang);
                            }

                            for (i=0; i<highlightcnt; i++)
                            {
                                int32_t x=HLMEMBER(i, x), y=HLMEMBER(i, y);

                                isc_transform(&x, &y);

                                draw_square(x, y, 2, editorcolors[15]);

                                if ((highlight[i]&16384)==0)
                                {
                                    walltype const * const wal = &wall[highlight[i]];
                                    const int32_t p2=wal->point2, hlp=(show2dwall[p2>>3]&pow2char[p2&7]);
                                    vec3_t v1 = { x, y, 0 }, v2 = { wall[p2].x, wall[p2].y, 0 };

                                    isc_transform(&v2.x, &v2.y);
                                    if (!hlp)
                                    {
                                        v2.x = wall[p2].x;
                                        v2.y = wall[p2].y;
                                    }

                                    drawlinebetween(&v1, &v2, !hlp ? 8 :
                                                    editorcolors[wal->nextwall >= 0 ? 12 : 7],
                                                    0x11111111);
                                }
                            }
                        }
                        else
                        {
                            // finish interactive scaling
                            isc.active = 0;

                            if ((!isc.rotatep && (isc.xsc!=1<<16 || isc.ysc!=1<<16)) ||
                                (isc.rotatep && (isc.ang!=0)))
                            {
                                for (i=0; i<highlightcnt; i++)
                                {
                                    int32_t *x=&HLMEMBER(i, x), *y=&HLMEMBER(i, y);

                                    isc_transform(x, y);

                                    if (isc.rotatep && (highlight[i]&16384))
                                    {
                                        spritetype *spr = &sprite[highlight[i]&16383];
                                        spr->ang = (spr->ang + isc.ang)&2047;
                                    }
                                }

                                if (!isc.rotatep)
                                    message("Highlights scaled by x=%.3f y=%.3f",
                                            (double)isc.xsc/65536, (double)isc.ysc/65536);
                                else
                                    message("Highlights rotated by %d BUILD degrees", isc.ang);

                                asksave = 1;
                            }
                            else
                                printmessage16(" ");
                        }
                    }
                }
            }
            else
            {
                if (isc.active)
                {
                    isc.active = 0;

                    printmessage16("Aborted interactive %s.", isc.rotatep ? "rotation" : "scaling");
                    bstatus &= ~3;
                    mousewaitmask = 3;
                    pointhighlight = -1;
                }
            }

            editorDraw2dLine(searchx,0, searchx,8, editorcolors[15]);
            editorDraw2dLine(0,searchy, 8,searchy, editorcolors[15]);

            // 2d3d mode
            if (m32_2d3dmode && m32_2d3d_resolutions_match())
            {
#ifdef USE_OPENGL
                int bakrendmode = rendmode;
#endif
                vec2_t bdim = { xdim, ydim };

                xdim = xdim2d;
                ydim = ydim2d;
#ifdef USE_OPENGL
                rendmode = REND_CLASSIC;
#endif

                if (m32_2d3d.x + XSIZE_2D3D > xdim2d - 4)
                    m32_2d3d.x = xdim2d - 4 - XSIZE_2D3D;

                if (m32_2d3d.y + YSIZE_2D3D > ydim2d - 4 - STATUS2DSIZ2)
                    m32_2d3d.y = ydim2d - 4 - YSIZE_2D3D - STATUS2DSIZ2;

                updatesectorz(pos.x, pos.y, pos.z, &cursectnum);

                if (cursectnum == -1)
                    updatesector(pos.x, pos.y, &cursectnum);

                if (cursectnum != -1)
                {
                    int32_t cz, fz;

                    getzsofslope(cursectnum, pos.x, pos.y, &cz, &fz);

                    inpclamp(&pos.z, cz+(4<<8), fz-(4<<8));

                    videoEndDrawing();
                    videoSetViewableArea(m32_2d3d.x, m32_2d3d.y, m32_2d3d.x + XSIZE_2D3D, m32_2d3d.y + YSIZE_2D3D);
                    videoClearViewableArea(-1);

                    vec2_t osearch = { searchx, searchy };

                    searchx -= m32_2d3d.x;
                    searchy -= m32_2d3d.y;

                    M32_DrawRoomsAndMasks();
                    videoSetViewableArea(0, 0, xdim2d-1, ydim2d-1);

#ifdef USE_OPENGL
                    rendmode = bakrendmode;
#endif
                    xdim = bdim.x;
                    ydim = bdim.y;
                    searchx = osearch.x;
                    searchy = osearch.y;

                    videoBeginDrawing();
                    editorDraw2dLine(m32_2d3d.x, m32_2d3d.y, m32_2d3d.x + XSIZE_2D3D, m32_2d3d.y, editorcolors[15]);
                    editorDraw2dLine(m32_2d3d.x + XSIZE_2D3D, m32_2d3d.y, m32_2d3d.x + XSIZE_2D3D, m32_2d3d.y  + YSIZE_2D3D, editorcolors[15]);
                    editorDraw2dLine(m32_2d3d.x, m32_2d3d.y, m32_2d3d.x, m32_2d3d.y + YSIZE_2D3D, editorcolors[15]);
                    editorDraw2dLine(m32_2d3d.x, m32_2d3d.y + YSIZE_2D3D, m32_2d3d.x + XSIZE_2D3D, m32_2d3d.y + YSIZE_2D3D, editorcolors[15]);
                }
            }

            if (!m32_is2d3dmode())
            {
                ////// draw mouse pointer

                col = editorcolors[0];

                drawline16base(searchx+1, searchy+1, +0, -8, +0, -1, col);
                drawline16base(searchx+1, searchy+1, +0, 1, +0, 8, col);

                drawline16base(searchx+1, searchy+1, -8, 0, -1, 0, col);
                drawline16base(searchx+1, searchy+1, 1, 0, 8, 0, col);

                col = searchlock ? editorcolors[13] : editorcolors[15 - 3*gridlock];

                if (joinsector[0] >= 0)
                    col = editorcolors[11];

                if (numcorruptthings>0)
                {
                    static char cbuf[64];

                    if ((pointhighlight&16384)==0)
                    {
                        // If aiming at wall, check whether it is corrupt, and print a
                        // warning message near the mouse pointer if that is the case.
                        for (i=0; i<numcorruptthings; i++)
                            if ((corruptthings[i]&CORRUPT_MASK)==CORRUPT_WALL &&
                                (corruptthings[i]&(MAXWALLS-1))==pointhighlight)
                            {
                                col = editorcolors[13];
                                printext16(searchx+6, searchy-6-8, editorcolors[13], editorcolors[0], "corrupt wall", 0);
                                break;
                            }
                    }

                    Bsprintf(cbuf, "Map corrupt (level %d): %s%d errors", corruptlevel,
                        numcorruptthings>=MAXCORRUPTTHINGS ? ">=" : "", numcorruptthings);
                    printext16(8, 8, editorcolors[13]+(M32_THROB>>2), editorcolors[0], cbuf, 0);
                }

                if (highlightsectorcnt==0 || highlightcnt==0)
                {
                    if (keystatus[sc_SemiColon] || keystatus[sc_Quote])  // ' and ;
                    {
                        col = editorcolors[14];

                        drawline16base(searchx+16, searchy-16, -4, 0, +4, 0, col);
                        if (keystatus[sc_Quote])
                            drawline16base(searchx+16, searchy-16, 0, -4, 0, +4, col);
                    }

                    if (highlightsectorcnt == 0)
                        if (keystatus[sc_RightShift])
                            printext16(searchx+6, searchy-2+8, editorcolors[12], -1, "ALL", 0);

                    if (highlightcnt == 0)
                    {
                        if (eitherCTRL && (highlight1.x!=highlight2.x || highlight1.y!=highlight2.y))
                            printext16(searchx+6, searchy-6-8, editorcolors[12], -1, "SPR ONLY", 0);
#ifdef YAX_ENABLE
                        if (keystatus[sc_End])  // End
                            printext16(searchx+6, searchy-2+8, editorcolors[12], -1, "ALL", 0);
#endif
                    }
                }

                drawline16base(searchx, searchy, +0, -8, +0, -1, col);
                drawline16base(searchx, searchy, +0, 1, +0, 8, col);

                drawline16base(searchx, searchy, -8, 0, -1, 0, col);
                drawline16base(searchx, searchy, 1, 0, 8, 0, col);

                ////// Draw the white pixel closest to mouse cursor on linehighlight
                if (linehighlight>=0)
                {
                    char col = wall[linehighlight].nextsector >= 0 ? editorcolors[15] : editorcolors[5];

                    if (m32_sideview)
                    {
                        getclosestpointonwall(searchx, searchy, linehighlight, &dax, &day, 1);
                        drawline16base(dax, day, 0, 0, 0, 0, col);
                    }
                    else
                    {
                        getclosestpointonwall(mousxplc, mousyplc, linehighlight, &dax, &day, 0);
                        ovhscrcoords(dax, day, &x2, &y2);
                        drawline16base(x2, y2, 0, 0, 0, 0, col);
                    }
                }
            }

            videoEndDrawing();  //}}} LOCK_FRAME_1

            OSD_Draw();
        }

        inputchecked = 1;


        VM_OnEvent(EVENT_PREKEYS2D, -1);
        CallExtCheckKeys(); // TX 20050101, it makes more sense to have this here so keys can be overwritten with new functions in bstub.c

        // 2d3d mode
        if (m32_is2d3dmode())
            goto nokeys;

        // Flip/mirror sector Ed Coolidge
        if (keystatus[sc_X] || keystatus[sc_Y])  // X or Y (2D)
        {
            int32_t about_x=keystatus[sc_X];
            int32_t doMirror = eitherALT;  // mirror walls and wall/floor sprites

#ifdef YAX_ENABLE
            if (highlightsectorcnt > 0 && !hl_all_bunch_sectors_p())
            {
                printmessage16("To flip extended sectors, all sectors of a bunch must be selected");
                keystatus[sc_X] = keystatus[sc_Y] = 0;
            }
            else
#endif
            if (highlightsectorcnt > 0)
            {
                int16_t *const otonwall = onextwall;  // OK, since we make old-nextwalls invalid

                mkonwinvalid();

                keystatus[sc_X] = keystatus[sc_Y] = 0;

                for (j=0; j<numwalls; j++)
                    otonwall[j] = j;

                get_sectors_center(highlightsector, highlightsectorcnt, &dax, &day);

                if (gridlock && grid > 0)
                    locktogrid(&dax, &day);

                for (i=0; i<highlightsectorcnt; i++)
                {
                    int32_t startofloop, endofloop;
                    int32_t numtoswap = -1;
                    int32_t w=0;
                    uwalltype tempwall;

                    startofloop = startwall = sector[highlightsector[i]].wallptr;
                    endofloop = endwall = startwall+sector[highlightsector[i]].wallnum-1;
#if 0
                    if (doMirror)
                    {
                        //mirror sector textures
                        sector[highlightsector[i]].ceilingstat ^= 0x10;
                        sector[highlightsector[i]].floorstat ^= 0x10;
                    }
#endif
                    //save position of wall at start of loop
                    x3 = wall[startofloop].x;
                    y3 = wall[startofloop].y;

                    for (j=startwall; j<=endwall; j++)
                    {
                        //fix position of walls
                        if (about_x)
                        {
                            wall[j].x = dax-POINT2(j).x+dax; //flip wall.x about dax
                            wall[j].y = POINT2(j).y;
                        }
                        else
                        {
                            wall[j].x = POINT2(j).x;
                            wall[j].y = day-POINT2(j).y+day; //flip wall.y about day
                        }

                        if (doMirror)
                            wall[j].cstat ^= 8;  //mirror walls about dax/day

                        if (wall[j].point2==startofloop) //check if j is end of loop
                        {
                            endofloop = j;
                            if (about_x)
                            {
                                wall[endofloop].x = dax-x3+dax; //flip wall.x about dax
                                wall[endofloop].y = y3;
                            }
                            else
                            {
                                wall[endofloop].x = x3;
                                wall[endofloop].y = day-y3+day; //flip wall.y about dax
                            }

                            //correct order of walls in loop to maintain player space (right-hand rule)
                            numtoswap = (endofloop-startofloop)>>1;
                            for (w=1; w<=numtoswap; w++)
                            {
                                Bmemcpy(&tempwall, &wall[startofloop+w], sizeof(walltype));
                                Bmemcpy(&wall[startofloop+w], &wall[endofloop-w+1], sizeof(walltype));
                                Bmemcpy(&wall[endofloop-w+1], &tempwall, sizeof(walltype));

                                otonwall[startofloop+w] = endofloop-w+1;
                                otonwall[endofloop-w+1] = startofloop+w;
                            }

                            //make point2 point to next wall in loop
                            for (w=startofloop; w<endofloop; w++)
                                wall[w].point2 = w+1;
                            wall[endofloop].point2 = startofloop;

                            startofloop = endofloop+1; //set first wall of next loop
                            //save position of wall at start of loop
                            x3 = wall[startofloop].x;
                            y3 = wall[startofloop].y;
                        }
                    }

                    j = headspritesect[highlightsector[i]];
                    while (j != -1)
                    {
                        if (about_x)
                        {
                            x3 = sprite[j].x;
                            sprite[j].x = dax-x3+dax; //flip sprite.x about dax
                            sprite[j].ang = (1024+2048-sprite[j].ang)&2047; //flip ang about 512
                        }
                        else
                        {
                            y3 = sprite[j].y;
                            sprite[j].y = day-y3+day; //flip sprite.y about day
                            sprite[j].ang = (2048-sprite[j].ang)&2047; //flip ang about 512
                        }

                        if (doMirror && (sprite[j].cstat & 0x30))
                            sprite[j].cstat ^= 4;  // mirror sprites about dax/day (don't mirror monsters)

                        j = nextspritesect[j];
                    }
                }

                // finally, construct the nextwalls and yax-nextwalls
                // for the new arrangement!
                for (i=0; i<highlightsectorcnt; i++)
                {
                    for (WALLS_OF_SECTOR(highlightsector[i], j))
                    {
                        if (wall[j].nextwall >= 0)
                            wall[j].nextwall = otonwall[wall[j].nextwall];
#ifdef YAX_ENABLE
                        {
                            int32_t cf, ynw;
                            for (cf=0; cf<2; cf++)
                                if ((ynw = yax_getnextwall(j, cf)) >= 0)
                                    yax_setnextwall(j, cf, otonwall[ynw]);
                        }
#endif
                    }
                }

                printmessage16("Selected sector(s) flipped");
                asksave = 1;
            }
        }
        // end edit for sector flip

        if (keystatus[88])   //F12
        {
            keystatus[88] = 0;
//__clearscreen_beforecapture__
            videoCaptureScreen("captxxxx.tga", eitherSHIFT);

            videoShowFrame(1);
        }
        if (keystatus[sc_B])  // B (clip Blocking xor) (2D)
        {
            pointhighlight = getpointhighlight(mousxplc, mousyplc, pointhighlight);
            linehighlight = getlinehighlight(mousxplc, mousyplc, linehighlight, 0);

            if ((pointhighlight&0xc000) == 16384)
            {
                sprite[pointhighlight&16383].cstat ^= 1;
                sprite[pointhighlight&16383].cstat &= ~256;
                sprite[pointhighlight&16383].cstat |= ((sprite[pointhighlight&16383].cstat&1)<<8);
                asksave = 1;
            }
            else if (linehighlight >= 0)
            {
                wall[linehighlight].cstat ^= 1;
                wall[linehighlight].cstat &= ~64;
                if ((wall[linehighlight].nextwall >= 0) && !eitherSHIFT)
                {
                    NEXTWALL(linehighlight).cstat &= ~(1+64);
                    NEXTWALL(linehighlight).cstat |= (wall[linehighlight].cstat&1);
                }
                asksave = 1;
            }
            keystatus[sc_B] = 0;
        }
        if (keystatus[sc_F])  //F (F alone does nothing in 2D right now)
        {
            keystatus[sc_F] = 0;
            if (eitherALT)  //ALT-F (relative alignmment flip)
            {
                linehighlight = getlinehighlight(mousxplc, mousyplc, linehighlight, 0);
                if (linehighlight >= 0)
                    SetFirstWall(sectorofwall(linehighlight), linehighlight, 1);
            }
        }

        if (keystatus[sc_O])  // O (ornament onto wall) (2D)
        {
            keystatus[sc_O] = 0;
            if ((pointhighlight&0xc000) == 16384)
            {
                asksave = 1;
                DoSpriteOrnament(pointhighlight&16383);
            }
        }


        tsign = 0;
        if (keystatus[sc_Comma] || (bstatus&33)==33)  // , (2D)
            tsign = +1;
        if (keystatus[sc_Period] || (bstatus&17)==17)  // . (2D)
            tsign = -1;

        if (tsign)
        {
#ifdef YAX_ENABLE
            if (highlightsectorcnt > 0 && !hl_all_bunch_sectors_p())
            {
                printmessage16("To rotate ext. sectors, all sectors of a bunch must be selected");
            }
            else
#endif
            if (highlightsectorcnt > 0)
            {
                int32_t smoothRotation = eitherSHIFT, manualAngle = eitherALT;
                vec2_t da = { dax, day };

                if (manualAngle)
                {
                    tsign = getnumber16("Rotation BUILD angle: ", 0, 2047, 1);
                    if (tsign==0)
                    {
                        printmessage16(" ");
                        goto rotate_hlsect_out;
                    }

                    printmessage16("Rotated highlighted sectors by %d BUILD degrees", tsign);
                    tsign &= 2047;
                    smoothRotation = 1;
                }

                get_sectors_center(highlightsector, highlightsectorcnt, &da.x, &da.y);

                if (!smoothRotation)
                {
                    if (gridlock && grid > 0)
                        locktogrid(&da.x, &da.y);

                    tsign *= 512;
                }


                for (i=0; i<highlightsectorcnt; i++)
                {
                    for (WALLS_OF_SECTOR(highlightsector[i], j))
                        rotatepoint(da, wall[j].pos, tsign&2047, &wall[j].pos);

                    for (j=headspritesect[highlightsector[i]]; j != -1; j=nextspritesect[j])
                    {
                        rotatepoint(da, sprite[j].pos.vec2, tsign&2047, &sprite[j].pos.vec2);
                        sprite[j].ang = (sprite[j].ang+tsign)&2047;
                    }
                }

                m32_rotateang += tsign;
                m32_rotateang &= 2047;
                asksave = 1;
rotate_hlsect_out:
                if (!smoothRotation || manualAngle)
                    keystatus[sc_Comma] = keystatus[sc_Period] = 0;

                g_mouseBits &= ~(16|32);
                bstatus &= ~(16|32);
            }
            else
            {
                if (pointhighlight >= 16384)
                {
                    i = pointhighlight-16384;
                    if (eitherSHIFT)
                        sprite[i].ang = (sprite[i].ang-tsign)&2047;
                    else
                    {
                        sprite[i].ang = (sprite[i].ang-128*tsign)&2047;
                        keystatus[sc_Comma] = keystatus[sc_Period] = 0;
                    }

                    g_mouseBits &= ~(16|32);
                    bstatus &= ~(16|32);
                }
            }
        }

        if (keystatus[sc_ScrollLock])  //Scroll lock (set starting position)
        {
            startpos = pos;
            startang = ang;
            startsectnum = cursectnum;
            keystatus[sc_ScrollLock] = 0;
            asksave = 1;

            printmessage16("Set starting position");
        }
#if 1
        if (keystatus[sc_F5])  //F5
        {
            CallExtShowSectorData(-1);
        }
        if (keystatus[sc_F6])  //F6
        {
            if (pointhighlight >= 16384)
                CallExtShowSpriteData(pointhighlight-16384);
            else if (linehighlight >= 0)
                CallExtShowWallData(linehighlight);
            else
                CallExtShowWallData(-1);
        }
        if (keystatus[sc_F7])  //F7
        {
            keystatus[sc_F7] = 0;

            for (i=0; i<numsectors; i++)
                if (inside_editor_curpos(i) == 1)
                {
                    YAX_SKIPSECTOR(i);

                    CallExtEditSectorData(i);
                    break;
                }
        }
        if (keystatus[sc_F8])  //F8
        {
            keystatus[sc_F8] = 0;

            if (pointhighlight >= 16384)
                CallExtEditSpriteData(pointhighlight-16384);
            else if (linehighlight >= 0)
                CallExtEditWallData(linehighlight);
        }
#endif

        if (keystatus[sc_H])  //H (Hi 16 bits of tag)
        {
            keystatus[sc_H] = 0;
            if (eitherCTRL)  //Ctrl-H
            {
                pointhighlight = getpointhighlight(mousxplc, mousyplc, pointhighlight);
                linehighlight = getlinehighlight(mousxplc, mousyplc, linehighlight, 0);

                if ((pointhighlight&0xc000) == 16384)
                {
                    sprite[pointhighlight&16383].cstat ^= 256;
                    asksave = 1;
                }
                else if (linehighlight >= 0)
                {
                    wall[linehighlight].cstat ^= 64;
                    if ((wall[linehighlight].nextwall >= 0) && !eitherSHIFT)
                    {
                        NEXTWALL(linehighlight).cstat &= ~64;
                        NEXTWALL(linehighlight).cstat |= (wall[linehighlight].cstat&64);
                    }
                    asksave = 1;
                }
            }
            else if (eitherALT)  //ALT
            {
                if (pointhighlight >= 16384)
                {
                    i = pointhighlight-16384;
                    j = taglab_linktags(1, i);
                    j = 2*(j&2);
                    Bsprintf(buffer, "Sprite (%d) Hi-tag: ", i);
                    sprite[i].hitag = getnumber16(buffer, sprite[i].hitag, BTAG_MAX, 0+j);
                }
                else if (linehighlight >= 0)
                {
                    i = linehighlight;
                    j = taglab_linktags(1, i);
                    j = 2*(j&2);
                    Bsprintf(buffer, "Wall (%d) Hi-tag: ", i);
                    wall[i].hitag = getnumber16(buffer, wall[i].hitag, BTAG_MAX, 0+j);
                }
            }
            else
            {
                for (i=0; i<numsectors; i++)
                    if (inside_editor_curpos(i) == 1)
                    {
                        YAX_SKIPSECTOR(i);

                        Bsprintf(buffer, "Sector (%d) Hi-tag: ", i);
                        sector[i].hitag = getnumber16(buffer, sector[i].hitag, BTAG_MAX, 0);
                        break;
                    }
            }
            // printmessage16("");
        }
        if (keystatus[sc_P])  // P (palookup #)
        {
            keystatus[sc_P] = 0;

            for (i=0; i<numsectors; i++)
                if (inside_editor_curpos(i) == 1)
                {
                    YAX_SKIPSECTOR(i);

                    Bsprintf(buffer, "Sector (%d) Ceilingpal: ", i);
                    sector[i].ceilingpal = getnumber16(buffer, sector[i].ceilingpal, M32_MAXPALOOKUPS, 0);

                    Bsprintf(buffer, "Sector (%d) Floorpal: ", i);
                    sector[i].floorpal = getnumber16(buffer, sector[i].floorpal, M32_MAXPALOOKUPS, 0);
                    break;
                }
        }
        if (keystatus[sc_E])  // E (status list)
        {
            keystatus[sc_E] = 0;

            if (!eitherCTRL)
            {
                if (pointhighlight >= 16384)
                {
                    i = pointhighlight-16384;
                    Bsprintf(buffer, "Sprite (%d) Status list: ", i);
                    changespritestat(i, getnumber16(buffer, sprite[i].statnum, MAXSTATUS-1, 0));
                }
            }
#ifdef YAX_ENABLE
            else if (highlightsectorcnt > 0 && newnumwalls < 0)
            {
                ////////// YAX //////////
                static const char *cfs[2] = {"ceiling", "floor"};

                int32_t cf, thez, ulz[2] = {0,0};
                int16_t bn, sandwichbunch=-1;

                if (numyaxbunches==YAX_MAXBUNCHES)
                {
                    message("Bunch limit of %d reached, cannot extend", YAX_MAXBUNCHES);
                    goto end_yax;
                }

                if (highlighted_sectors_components(0,0) != 1)
                {
                    message("Sectors to extend must be in one connected component");
                    goto end_yax;
                }

                cf = ask_above_or_below();
                if (cf==-1)
                    goto end_yax;

                thez = SECTORFLD(highlightsector[0],z, cf);
                for (i=0; i<highlightsectorcnt; i++)
                {
                    bn = yax_getbunch(highlightsector[i], cf);

                    if (sandwichbunch >= 0 && bn!=sandwichbunch)
                    {
                        message("When sandwiching extension, must select only sectors of one bunch");
                        goto end_yax;
                    }

                    if (bn >= 0)
                    {
                        if (cf==YAX_FLOOR)
                        {
                            if (sandwichbunch < 0 && i!=0)
                            {
                                message("When sandwiching extension, must select only sectors of the bunch");
                                goto end_yax;
                            }
                            sandwichbunch = bn;
                        }
                        else
                        {
                            message("Sector %d's %s is already extended", highlightsector[i], cfs[cf]);
                            goto end_yax;
                        }
                    }

                    if (SECTORFLD(highlightsector[i],z, cf) != thez)
                    {
                        message("Sector %d's %s height doesn't match sector %d's",
                                highlightsector[i], cfs[cf], highlightsector[0]);
                        goto end_yax;
                    }

                    if ((sandwichbunch>=0 || highlightsectorcnt>1) && SECTORFLD(highlightsector[i],stat, cf)&2)
                    {
                        message("Sector %ss must not be sloped%s", cfs[cf],
                                sandwichbunch>=0 ? "" : "if extending more than one");
                        goto end_yax;
                    }
                }

                if (sandwichbunch >= 0)
                {
                    // cf==YAX_FLOOR here

                    int32_t tempz, oldfz, swsecheight = DEFAULT_YAX_HEIGHT/4;
                    // highest floor z of lower sectors, lowest ceiling z of these sectors
                    int32_t minfloorz = INT32_MAX, maxceilz = INT32_MIN;

                    // some preparation for making the sandwich
                    if (highlightsectorcnt != yax_numsectsinbunch(sandwichbunch, YAX_FLOOR))
                    {
                        message("When sandwiching extension, must select all sectors of the bunch");
                        goto end_yax;
                    }

                    // "for i in sectors of sandwichbunch(floor)" is now the same as
                    // "for i in highlighted sectors"

                    oldfz = sector[highlightsector[0]].floorz;

                    // check if enough room in z
                    for (SECTORS_OF_BUNCH(sandwichbunch, YAX_CEILING, i))
                        for (WALLS_OF_SECTOR(i, j))
                        {
                            tempz = getflorzofslope(i, wall[j].x, wall[j].y);
                            minfloorz = min(minfloorz, tempz);
                        }
                    for (SECTORS_OF_BUNCH(sandwichbunch, YAX_FLOOR, i))
                        for (WALLS_OF_SECTOR(i, j))
                        {
                            tempz = getceilzofslope(i, wall[j].x, wall[j].y);
                            maxceilz = max(maxceilz, tempz);
                        }

                    if (minfloorz - maxceilz < 2*swsecheight)
                    {
                        message("Too little z headroom for sandwiching, need at least %d",
                                2*swsecheight);
                        goto end_yax;
                    }

                    if (maxceilz >= oldfz || oldfz >= minfloorz)
                    {
                        message("Internal error while sandwiching: oldfz out of bounds");
                        goto end_yax;
                    }

                    // maxceilz   ---|
                    //   ^           |
                    // ulz[0]        ^
                    //   ^          oldfz
                    // ulz[1]        ^
                    //   ^           |
                    // minfloorz  ---|

                    ulz[0] = (int32_t)(oldfz - swsecheight*((double)(oldfz-maxceilz)/(minfloorz-maxceilz)));
                    ulz[0] &= ~255;
                    ulz[1] = ulz[0] + swsecheight;

                    if (maxceilz >= ulz[0] || ulz[1] >= minfloorz)
                    {
                        message("Too little z headroom for sandwiching");
                        goto end_yax;
                    }
                }

                m = numwalls;
                Bmemset(visited, 0, sizeof(visited));
                // construct!
                for (i=0; i<highlightsectorcnt; i++)
                    for (WALLS_OF_SECTOR(highlightsector[i], j))
                    {
                        k = trace_loop(j, visited, NULL, NULL, !cf);
                        if (k == 0)
                            continue;
                        else if (k < 0)
                        {
                            numwalls = m;
                            goto end_yax;
                        }
//message("loop");
                        wall[k-1].point2 = numwalls;
                        numwalls = k;
                    }

                for (i=m; i<numwalls; i++)  // try
                {
                    j = YAX_NEXTWALL(i, !cf);
                    if (j < 0)
                    {
                        message("Internal error while constructing sector: "
                                "YAX_NEXTWALL(%d, %d)<0!", i, !cf);
                        numwalls = m;
                        goto end_yax;
                    }
                    if (sandwichbunch >= 0)
                    {
                        if (YAX_NEXTWALL(j, cf) < 0)
                        {
                            message("Internal error while sandwiching (2): "
                                    "YAX_NEXTWALL(%d, %d)<0!", j, cf);
                            numwalls = m;
                            goto end_yax;
                        }
                    }
                }
                for (i=m; i<numwalls; i++)  // do!
                {
                    j = YAX_NEXTWALL(i, !cf);

                    if (sandwichbunch >= 0)
                    {
                        int16_t oynw = YAX_NEXTWALL(j, cf);
                        yax_setnextwall(j, cf, i);
                        yax_setnextwall(i, cf, oynw);
                        yax_setnextwall(oynw, !cf, i);
                    }
                    else
                    {
                        yax_setnextwall(j, cf, i);
                    }
                }

                // create new sector based on first highlighted one
                i = highlightsector[0];
                Bmemcpy(&sector[numsectors], &sector[i], sizeof(sectortype));
                sector[numsectors].wallptr = m;
                sector[numsectors].wallnum = numwalls-m;

                if (sandwichbunch < 0)
                {
                    if (SECTORFLD(i,stat, cf)&2)
                        setslope(numsectors, !cf, SECTORFLD(i,heinum, cf));
                    else
                        setslope(numsectors, !cf, 0);
                    setslope(numsectors, cf, 0);

                    SECTORFLD(numsectors,z, !cf) = SECTORFLD(i,z, cf);
                    SECTORFLD(numsectors,z, cf) = SECTORFLD(i,z, cf) - (1-2*cf)*DEFAULT_YAX_HEIGHT;
                }
                else
                {
                    for (SECTORS_OF_BUNCH(sandwichbunch, cf, i))
                        sector[i].floorz = ulz[0];
                    sector[numsectors].ceilingz = ulz[0];
                    sector[numsectors].floorz = ulz[1];
                    for (SECTORS_OF_BUNCH(sandwichbunch, !cf, i))
                        sector[i].ceilingz = ulz[1];
                }

                newnumwalls = numwalls;
                numwalls = m;

                SECTORFLD(numsectors,stat, !cf) &= ~1;  // no plax

                // restore red walls of the selected sectors
                for (i=0; i<highlightsectorcnt; i++)
                {
                    SECTORFLD(highlightsector[i],stat, cf) &= ~1;  // no plax

                    for (WALLS_OF_SECTOR(highlightsector[i], j))
                        if (wall[j].nextwall < 0)
                            checksectorpointer(j, highlightsector[i]);
                }

                // link
                if (sandwichbunch < 0)
                {
                    yax_setbunch(numsectors, !cf, numyaxbunches);
                    for (i=0; i<highlightsectorcnt; i++)
                        yax_setbunch(highlightsector[i], cf, numyaxbunches);
                }
                else
                {
                    yax_setbunch(numsectors, !cf, sandwichbunch);
                    // also relink
                    yax_setbunch(numsectors, cf, numyaxbunches);
                    for (SECTORS_OF_BUNCH(sandwichbunch, !cf, i))
                        yax_setbunch(i, !cf, numyaxbunches);
                }

                numwalls = newnumwalls;
                newnumwalls = -1;

                numsectors++;
                yax_update(0);
                yax_updategrays(pos.z);

                reset_highlightsector();

                if (sandwichbunch < 0)
                    message("Extended %ss of highlighted sectors, creating bunch %d",
                            cfs[cf], numyaxbunches-1);
                else
                    message("Sandwiched bunch %d, creating bunch %d",
                            sandwichbunch, numyaxbunches-1);
                asksave = 1;
            }
            else if (highlightcnt > 0)
            {
                /// 'punch' wall loop through extension

                int32_t loopstartwall = -1, numloopwalls, cf;
                int32_t srcsect, dstsect, ofirstwallofs;
                int16_t cb, fb, bunchnum;

                if (EDITING_MAP_P())
                {
                    printmessage16("Must not be editing map to punch loop");
                    goto end_yax;
                }

                if (numyaxbunches >= YAX_MAXBUNCHES)
                {
                    message("TROR bunch limit reached, cannot punch loop");
                    goto end_yax;
                }

                // determine start wall
                for (i=0; i<highlightcnt; i++)
                {
                    j = highlight[i];

                    if (j&16384)
                        continue;

                    // we only want loop-starting walls
                    if (j>0 && wall[j-1].point2==j)
                        continue;

                    if (clockdir(j)==CLOCKDIR_CCW)
                    {
                        YAX_SKIPWALL(j);

                        if (loopstartwall >= 0)
                        {
                            message("Must have a unique highlighted CCW loop to punch");
                            goto end_yax;
                        }

                        loopstartwall = j;
                    }
                }

                if (loopstartwall == -1)
                {
                    message("Didn't find any non-grayed out CCW loop start walls");
                    goto end_yax;
                }

                // determine sector
                srcsect = sectorofwall(loopstartwall);
                yax_getbunches(srcsect, &cb, &fb);
                if (cb < 0 && fb < 0)
                {
                    message("Ceiling or floor must be extended to punch loop");
                    goto end_yax;
                }

                /// determine c/f
                cf = -1;
                if (fb < 0)
                    cf = YAX_CEILING;
                else if (cb < 0)
                    cf = YAX_FLOOR;

                fade_editor_screen(-1);

                // query top/bottom
                if (cf == -1)
                {
                    char dachars[2] = {'a', 'z'};
                    cf = editor_ask_function("Punch loop above (a) or below (z)?", dachars, 2);
                    if (cf == -1)
                        goto end_yax;
                }
                else
                {
                    // ask even if only one choice -- I find it more
                    // consistent with 'extend sector' this way
                    if (-1 == editor_ask_function(cf==YAX_CEILING ? "Punch loop above (a)?" :
                                                  "Punch loop below (z)?", cf==YAX_CEILING?"a":"z", 1))
                        goto end_yax;
                }

                bunchnum = (cf==YAX_CEILING) ? cb : fb;

                // check 1
                j = loopstartwall;  // will be real start wall of loop
                numloopwalls = 1;  // will be number of walls in loop
                for (i=wall[loopstartwall].point2; i!=loopstartwall; i=wall[i].point2)
                {
                    numloopwalls++;
                    if (i < j)
                        j = i;

                    if ((show2dwall[i>>3]&pow2char[i&7])==0)
                    {
                        message("All loop points must be highlighted to punch");
                        goto end_yax;
                    }

                    if (yax_getnextwall(loopstartwall, cf) >= 0 || yax_getnextwall(i, cf) >= 0)
                    {
                        // somewhat redundant, since it would also be caught by check 2
                        message("Loop walls must not already have TROR neighbors");
                        goto end_yax;
                    }

                    if (wall[loopstartwall].nextwall < 0 || wall[i].nextwall < 0)
                    {
                        message("INTERNAL ERROR: All loop walls are expected to be red");
                        goto end_yax;
                    }
                }
                loopstartwall = j;

                if (numwalls + 2*numloopwalls > MAXWALLS || numsectors+1 > MAXSECTORS)
                {
                    message("Punching loop through extension would exceed limits");
                    goto end_yax;
                }

                // get other-side sector, j==loopstartwall
                dstsect = yax_getneighborsect(wall[j].x, wall[j].y, srcsect, cf);
                if (dstsect < 0)
                {
                    message("Punch loop INTERNAL ERROR: dstsect < 0. Map corrupt?");
                    goto end_yax;
                }

                // check 2
                i = loopstartwall;
                do
                {
                    j = wall[i].point2;

                    for (WALLS_OF_SECTOR(dstsect, k))
                    {
                        vec2_t pint;
                        if (lineintersect2v(&wall[i].pos, &wall[j].pos, &wall[k].pos, &POINT2(k).pos, &pint))
                        {
                            message("Loop lines must not intersect any destination sector's walls");
                            goto end_yax;
                        }
                    }
                }
                while ((i = j) != loopstartwall);

                // construct new loop and (dummy yet) sector
                Bmemcpy(&wall[numwalls], &wall[loopstartwall], numloopwalls*sizeof(walltype));
                newnumwalls = numwalls+numloopwalls;

                for (i=numwalls; i<newnumwalls; i++)
                {
                    wall[i].point2 += (numwalls - loopstartwall);
                    wall[i].nextsector = wall[i].nextwall = -1;
                }

                sector[numsectors].wallptr = numwalls;
                sector[numsectors].wallnum = numloopwalls;
                numsectors++; // temp

                // check 3
                for (SECTORS_OF_BUNCH(bunchnum, !cf, i))
                    for (WALLS_OF_SECTOR(i, j))
                    {
                        if (inside(wall[j].x, wall[j].y, numsectors-1)==1)
                        {
                            numsectors--;
                            newnumwalls = -1;
                            message("A point of bunch %d's sectors lies inside the loop to punch",
                                    bunchnum);
                            goto end_yax;
                        }
                    }

                numsectors--;

                // clear wall & sprite highlights
                //  TODO: see about consistency with update_highlight() after other ops
                reset_highlight();

                // construct the loop!
                i = AddLoopToSector(dstsect, &ofirstwallofs);

                if (i <= 0)
                {
                    message("Punch loop INTERNAL ERROR with AddLoopToSector!");
                }
                else
                {
                    int32_t oneinnersect = -1, innerdstsect = numsectors-1;

                    if (dstsect < srcsect)
                        loopstartwall += numloopwalls;

                    /// handle bunchnums! (specifically, create a new one)

                    // collect sectors inside source loop; for that, first break the
                    // inner->outer nextwall links
                    for (i=loopstartwall; i<loopstartwall+numloopwalls; i++)
                    {
                        // all src loop walls are red!
                        NEXTWALL(i).nextwall = NEXTWALL(i).nextsector = -1;
                        oneinnersect = wall[i].nextsector;
                    }

                    // vvv
                    // expect oneinnersect >= 0 here!  Assumption: we collect exactly
                    // one connected component of sectors
                    collect_sectors1(collsectlist[0], collsectbitmap[0],
                                     &collnumsects[0], oneinnersect, 0, 0);

                    // set new bunchnums
                    for (i=0; i<collnumsects[