mirror of
https://github.com/DrBeef/Raze.git
synced 2024-11-16 01:11:28 +00:00
a7fb2721e4
DONT_BUILD. git-svn-id: https://svn.eduke32.com/eduke32@4561 1a8010ca-5511-0410-912e-c29ae57300e0
1877 lines
56 KiB
C
1877 lines
56 KiB
C
|
|
#include "compat.h"
|
|
#include "keys.h"
|
|
#include "build.h"
|
|
#include "cache1d.h"
|
|
#include "polymer.h"
|
|
#include "editor.h"
|
|
#include "renderlayer.h"
|
|
|
|
#include "m32script.h"
|
|
#include "m32def.h"
|
|
|
|
#include "lz4.h"
|
|
#include "xxhash.h"
|
|
|
|
//////////////////// Key stuff ////////////////////
|
|
|
|
#define eitherALT (keystatus[KEYSC_LALT] || keystatus[KEYSC_RALT])
|
|
#define eitherCTRL (keystatus[KEYSC_LCTRL] || keystatus[KEYSC_RCTRL])
|
|
#define eitherSHIFT (keystatus[KEYSC_LSHIFT] || keystatus[KEYSC_RSHIFT])
|
|
|
|
#define PRESSED_KEYSC(Key) (keystatus[KEYSC_##Key] && !(keystatus[KEYSC_##Key]=0))
|
|
|
|
////
|
|
|
|
// All these variables need verification that all relevant editor stubs are actually implementing them correctly.
|
|
|
|
char getmessage[162], getmessageleng;
|
|
int32_t getmessagetimeoff; //, charsperline;
|
|
|
|
int32_t mousxplc, mousyplc;
|
|
int32_t mouseaction;
|
|
|
|
char *scripthist[SCRIPTHISTSIZ];
|
|
int32_t scripthistend;
|
|
|
|
int32_t g_lazy_tileselector;
|
|
int32_t fixmaponsave_sprites = 1;
|
|
|
|
int32_t showambiencesounds=2;
|
|
|
|
int32_t autosave=180;
|
|
|
|
int32_t autocorruptcheck;
|
|
int32_t corruptcheck_noalreadyrefd;
|
|
int32_t corrupt_tryfix_alt;
|
|
int32_t corruptlevel, numcorruptthings, corruptthings[MAXCORRUPTTHINGS];
|
|
|
|
////
|
|
|
|
#ifdef YAX_ENABLE
|
|
const char *yupdownwall[2] = {"upwall","downwall"};
|
|
const char *YUPDOWNWALL[2] = {"UPWALL","DOWNWALL"};
|
|
#endif
|
|
|
|
////
|
|
|
|
void drawgradient(void)
|
|
{
|
|
int32_t i, col = whitecol-21;
|
|
begindrawing();
|
|
for (i=ydim-STATUS2DSIZ+16; i<ydim && col>0; i++,col--)
|
|
CLEARLINES2D(i, 1, (col<<24)|(col<<16)|(col<<8)|col);
|
|
CLEARLINES2D(i, ydim-i, 0);
|
|
enddrawing();
|
|
}
|
|
|
|
static void message_common1(const char *tmpstr)
|
|
{
|
|
Bstrncpyz(getmessage, tmpstr, sizeof(getmessage));
|
|
|
|
getmessageleng = Bstrlen(getmessage);
|
|
getmessagetimeoff = totalclock + 120*2 + getmessageleng*(120/30);
|
|
// lastmessagetime = totalclock;
|
|
}
|
|
|
|
void message(const char *fmt, ...)
|
|
{
|
|
char tmpstr[256];
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
Bvsnprintf(tmpstr, 256, fmt, va);
|
|
va_end(va);
|
|
|
|
message_common1(tmpstr);
|
|
|
|
if (!mouseaction)
|
|
OSD_Printf("%s\n", tmpstr);
|
|
}
|
|
|
|
void silentmessage(const char *fmt, ...)
|
|
{
|
|
char tmpstr[256];
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
Bvsnprintf(tmpstr, 256, fmt, va);
|
|
va_end(va);
|
|
|
|
message_common1(tmpstr);
|
|
}
|
|
|
|
////////// tag labeling system //////////
|
|
|
|
typedef struct
|
|
{
|
|
hashtable_t hashtab;
|
|
char *label[32768];
|
|
int32_t numlabels;
|
|
} taglab_t;
|
|
|
|
static taglab_t g_taglab;
|
|
|
|
static void tstrtoupper(char *s)
|
|
{
|
|
int32_t i;
|
|
for (i=0; s[i]; i++)
|
|
s[i] = Btoupper(s[i]);
|
|
}
|
|
|
|
void taglab_init()
|
|
{
|
|
int32_t i;
|
|
|
|
g_taglab.numlabels = 0;
|
|
g_taglab.hashtab.size = 16384;
|
|
hash_init(&g_taglab.hashtab);
|
|
|
|
for (i=0; i<32768; i++)
|
|
{
|
|
if (g_taglab.label[i])
|
|
Bfree(g_taglab.label[i]);
|
|
g_taglab.label[i] = NULL;
|
|
}
|
|
}
|
|
|
|
int32_t taglab_load(const char *filename, int32_t flags)
|
|
{
|
|
int32_t fil, len, i;
|
|
char buf[BMAX_PATH], *dot, *filebuf;
|
|
|
|
taglab_init();
|
|
|
|
len = Bstrlen(filename);
|
|
if (len >= BMAX_PATH-1)
|
|
return -1;
|
|
Bmemcpy(buf, filename, len+1);
|
|
|
|
//
|
|
dot = Bstrrchr(buf, '.');
|
|
if (!dot)
|
|
dot = &buf[len];
|
|
|
|
if (dot-buf+8 >= BMAX_PATH)
|
|
return -1;
|
|
Bmemcpy(dot, ".maptags", 9);
|
|
//
|
|
|
|
if ((fil = kopen4load(buf,flags)) == -1)
|
|
return -1;
|
|
|
|
len = kfilelength(fil);
|
|
|
|
filebuf = (char *)Xmalloc(len+1);
|
|
if (!filebuf)
|
|
{
|
|
kclose(fil);
|
|
return -1;
|
|
}
|
|
|
|
kread(fil, filebuf, len);
|
|
filebuf[len] = 0;
|
|
kclose(fil);
|
|
|
|
// ----
|
|
|
|
{
|
|
int32_t tag;
|
|
char *cp=filebuf, *bp, *ep;
|
|
|
|
while (1)
|
|
{
|
|
#define XTAGLAB_STRINGIFY(X) TAGLAB_STRINGIFY(X)
|
|
#define TAGLAB_STRINGIFY(X) #X
|
|
i = sscanf(cp, "%d %" XTAGLAB_STRINGIFY(TAGLAB_MAX) "s", &tag, buf);
|
|
#undef XTAGLAB_STRINGIFY
|
|
#undef TAGLAB_STRINGIFY
|
|
if (i != 2 || !buf[0] || tag<0 || tag>=32768)
|
|
goto nextline;
|
|
|
|
buf[TAGLAB_MAX-1] = 0;
|
|
|
|
i = Bstrlen(buf);
|
|
bp = buf; while (*bp && isspace(*bp)) bp++;
|
|
ep = &buf[i-1]; while (ep>buf && isspace(*ep)) ep--;
|
|
ep++;
|
|
|
|
if (!(ep > bp))
|
|
goto nextline;
|
|
*ep = 0;
|
|
|
|
taglab_add(bp, tag);
|
|
//initprintf("add tag %s:%d\n", bp, tag);
|
|
nextline:
|
|
while (*cp && *cp!='\n')
|
|
cp++;
|
|
while (*cp=='\r' || *cp=='\n')
|
|
cp++;
|
|
if (*cp == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ----
|
|
Bfree(filebuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t taglab_save(const char *mapname)
|
|
{
|
|
int32_t fil, len, i;
|
|
char buf[BMAX_PATH], *dot;
|
|
const char *label;
|
|
|
|
if (g_taglab.numlabels==0)
|
|
return 1;
|
|
|
|
Bstrncpyz(buf, mapname, BMAX_PATH);
|
|
|
|
len = Bstrlen(buf);
|
|
//
|
|
dot = Bstrrchr(buf, '.');
|
|
if (!dot)
|
|
dot = &buf[len];
|
|
|
|
if (dot-buf+8 >= BMAX_PATH)
|
|
return -1;
|
|
Bmemcpy(dot, ".maptags", 9);
|
|
//
|
|
|
|
if ((fil = Bopen(buf,BO_BINARY|BO_TRUNC|BO_CREAT|BO_WRONLY,BS_IREAD|BS_IWRITE)) == -1)
|
|
{
|
|
initprintf("Couldn't open \"%s\" for writing: %s\n", buf, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
for (i=0; i<32768; i++)
|
|
{
|
|
label = taglab_getlabel(i);
|
|
if (!label)
|
|
continue;
|
|
|
|
len = Bsprintf(buf, "%d %s" OURNEWL, i, label);
|
|
if (Bwrite(fil, buf, len)!=len)
|
|
break;
|
|
}
|
|
|
|
Bclose(fil);
|
|
|
|
return (i!=32768);
|
|
}
|
|
|
|
int32_t taglab_add(const char *label, int16_t tag)
|
|
{
|
|
const char *otaglabel;
|
|
char buf[TAGLAB_MAX];
|
|
int32_t olabeltag, diddel=0;
|
|
|
|
if (tag < 0)
|
|
return -1;
|
|
|
|
Bstrncpyz(buf, label, sizeof(buf));
|
|
// upcase the tag for storage and comparison
|
|
tstrtoupper(buf);
|
|
|
|
otaglabel = g_taglab.label[tag];
|
|
if (otaglabel)
|
|
{
|
|
if (!Bstrcasecmp(otaglabel, buf))
|
|
return 0;
|
|
|
|
// hash_delete(&g_taglab.hashtab, g_taglab.label[tag]);
|
|
|
|
// a label having the same tag number as 'tag' is deleted
|
|
Bfree(g_taglab.label[tag]);
|
|
g_taglab.label[tag] = NULL;
|
|
diddel |= 1;
|
|
}
|
|
else
|
|
{
|
|
olabeltag = hash_findcase(&g_taglab.hashtab, buf);
|
|
if (olabeltag==tag)
|
|
return 0;
|
|
|
|
if (olabeltag>=0)
|
|
{
|
|
// the label gets assigned to a new tag number ('tag deleted')
|
|
Bfree(g_taglab.label[olabeltag]);
|
|
g_taglab.label[olabeltag] = NULL;
|
|
diddel |= 2;
|
|
}
|
|
}
|
|
|
|
if (!diddel)
|
|
g_taglab.numlabels++;
|
|
g_taglab.label[tag] = Xstrdup(buf);
|
|
//initprintf("added %s %d to hash\n", g_taglab.label[tag], tag);
|
|
hash_add(&g_taglab.hashtab, g_taglab.label[tag], tag, 1);
|
|
|
|
return diddel;
|
|
}
|
|
|
|
const char *taglab_getlabel(int16_t tag)
|
|
{
|
|
if (tag < 0) // || tag>=32768 implicitly
|
|
return NULL;
|
|
|
|
return g_taglab.label[tag];
|
|
}
|
|
|
|
int32_t taglab_gettag(const char *label)
|
|
{
|
|
char buf[TAGLAB_MAX];
|
|
|
|
Bstrncpyz(buf, label, TAGLAB_MAX);
|
|
|
|
// need to upcase since hash_findcase doesn't work as expected:
|
|
// getting the code is still (necessarily) case-sensitive...
|
|
tstrtoupper(buf);
|
|
|
|
return hash_findcase(&g_taglab.hashtab, buf);
|
|
}
|
|
////////// end tag labeling system //////////
|
|
|
|
////////// UNDO/REDO SYSTEM //////////
|
|
#if M32_UNDO
|
|
mapundo_t *mapstate = NULL;
|
|
|
|
int32_t map_revision = 1;
|
|
|
|
#define QADDNSZ 400
|
|
|
|
|
|
static int32_t try_match_with_prev(int32_t idx, int32_t numsthgs, uint32_t crc)
|
|
{
|
|
if (mapstate->prev && mapstate->prev->num[idx]==numsthgs && mapstate->prev->crc[idx]==crc)
|
|
{
|
|
// found match!
|
|
mapstate->sws[idx] = mapstate->prev->sws[idx];
|
|
(*(int32_t *)mapstate->sws[idx])++; // increase refcount!
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void create_compressed_block(int32_t idx, const void *srcdata, uint32_t size, uint32_t crc)
|
|
{
|
|
uint32_t j;
|
|
|
|
// allocate
|
|
mapstate->sws[idx] = (char *)Xmalloc(4 + size + QADDNSZ);
|
|
|
|
// compress & realloc
|
|
j = LZ4_compress((const char*)srcdata, mapstate->sws[idx]+4, size);
|
|
mapstate->sws[idx] = (char *)Xrealloc(mapstate->sws[idx], 4 + j);
|
|
|
|
// write refcount
|
|
*(int32_t *)mapstate->sws[idx] = 1;
|
|
|
|
mapstate->crc[idx] = crc;
|
|
}
|
|
|
|
static void free_self_and_successors(mapundo_t *mapst)
|
|
{
|
|
mapundo_t *cur = mapst;
|
|
|
|
mapst->prev = NULL; // break the back link
|
|
|
|
while (cur->next)
|
|
cur = cur->next;
|
|
|
|
while (1)
|
|
{
|
|
int32_t i;
|
|
mapundo_t *const prev = cur->prev;
|
|
|
|
for (i=0; i<3; i++)
|
|
{
|
|
int32_t *const refcnt = (int32_t *)cur->sws[i];
|
|
|
|
if (refcnt)
|
|
{
|
|
(*refcnt)--;
|
|
if (*refcnt == 0)
|
|
Bfree(refcnt); // free the block!
|
|
}
|
|
}
|
|
|
|
Bfree(cur);
|
|
|
|
if (!prev)
|
|
break;
|
|
|
|
cur = prev;
|
|
}
|
|
}
|
|
|
|
// NOTE: only _consecutive_ matching (size+crc) sector/wall/sprite blocks are
|
|
// shared!
|
|
void create_map_snapshot(void)
|
|
{
|
|
if (mapstate == NULL)
|
|
{
|
|
// create initial mapstate
|
|
|
|
map_revision = 1;
|
|
|
|
mapstate = (mapundo_t *)Xcalloc(1, sizeof(mapundo_t));
|
|
mapstate->revision = map_revision;
|
|
mapstate->prev = mapstate->next = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (mapstate->next)
|
|
free_self_and_successors(mapstate->next);
|
|
// now, have no successors
|
|
|
|
// calloc because not everything may be set in the following:
|
|
mapstate->next = (mapundo_t *)Xcalloc(1, sizeof(mapundo_t));
|
|
mapstate->next->prev = mapstate;
|
|
|
|
mapstate = mapstate->next;
|
|
|
|
mapstate->revision = ++map_revision;
|
|
}
|
|
|
|
|
|
fixspritesectors();
|
|
|
|
mapstate->num[0] = numsectors;
|
|
mapstate->num[1] = numwalls;
|
|
mapstate->num[2] = Numsprites;
|
|
|
|
|
|
if (numsectors)
|
|
{
|
|
int32_t j;
|
|
uint32_t temphash = XXH32((uint8_t *)sector, numsectors*sizeof(sectortype), numsectors*sizeof(sectortype));
|
|
|
|
if (!try_match_with_prev(0, numsectors, temphash))
|
|
create_compressed_block(0, sector, numsectors*sizeof(sectortype), temphash);
|
|
|
|
if (numwalls)
|
|
{
|
|
temphash = XXH32((uint8_t *)wall, numwalls*sizeof(walltype), numwalls*sizeof(walltype));
|
|
|
|
if (!try_match_with_prev(1, numwalls, temphash))
|
|
create_compressed_block(1, wall, numwalls*sizeof(walltype), temphash);
|
|
}
|
|
|
|
if (Numsprites)
|
|
{
|
|
temphash = XXH32((uint8_t *)sprite, MAXSPRITES*sizeof(spritetype), MAXSPRITES*sizeof(spritetype));
|
|
|
|
if (!try_match_with_prev(2, Numsprites, temphash))
|
|
{
|
|
int32_t i = 0;
|
|
spritetype *const tspri = (spritetype *)Xmalloc(Numsprites*sizeof(spritetype) + 4);
|
|
spritetype *spri = tspri;
|
|
|
|
for (j=0; j<MAXSPRITES && i < Numsprites; j++)
|
|
if (sprite[j].statnum != MAXSTATUS)
|
|
{
|
|
Bmemcpy(spri++, &sprite[j], sizeof(spritetype));
|
|
i++;
|
|
}
|
|
|
|
create_compressed_block(2, tspri, Numsprites*sizeof(spritetype), temphash);
|
|
Bfree(tspri);
|
|
}
|
|
}
|
|
}
|
|
|
|
CheckMapCorruption(5, 0);
|
|
}
|
|
|
|
void map_undoredo_free(void)
|
|
{
|
|
if (mapstate)
|
|
{
|
|
free_self_and_successors(mapstate);
|
|
mapstate = NULL;
|
|
}
|
|
|
|
map_revision = 1;
|
|
}
|
|
|
|
int32_t map_undoredo(int32_t dir)
|
|
{
|
|
int32_t i;
|
|
|
|
if (mapstate == NULL) return 1;
|
|
|
|
if (dir)
|
|
{
|
|
if (mapstate->next == NULL || !mapstate->next->num[0]) return 1;
|
|
|
|
// while (map_revision+1 != mapstate->revision && mapstate->next)
|
|
mapstate = mapstate->next;
|
|
}
|
|
else
|
|
{
|
|
if (mapstate->prev == NULL || !mapstate->prev->num[0]) return 1;
|
|
|
|
// while (map_revision-1 != mapstate->revision && mapstate->prev)
|
|
mapstate = mapstate->prev;
|
|
}
|
|
|
|
numsectors = mapstate->num[0];
|
|
numwalls = mapstate->num[1];
|
|
map_revision = mapstate->revision;
|
|
|
|
Bmemset(show2dsector, 0, sizeof(show2dsector));
|
|
|
|
reset_highlightsector();
|
|
reset_highlight();
|
|
|
|
initspritelists();
|
|
|
|
if (mapstate->num[0])
|
|
{
|
|
// restore sector[]
|
|
LZ4_decompress_fast(mapstate->sws[0]+4, (char*)sector, numsectors*sizeof(sectortype));
|
|
|
|
if (mapstate->num[1]) // restore wall[]
|
|
LZ4_decompress_fast(mapstate->sws[1]+4, (char*)wall, numwalls*sizeof(walltype));
|
|
|
|
if (mapstate->num[2]) // restore sprite[]
|
|
LZ4_decompress_fast(mapstate->sws[2]+4, (char*)sprite, (mapstate->num[2])*sizeof(spritetype));
|
|
}
|
|
|
|
// insert sprites
|
|
for (i=0; i<mapstate->num[2]; i++)
|
|
{
|
|
if ((sprite[i].cstat & 48) == 48) sprite[i].cstat &= ~48;
|
|
Bassert((unsigned)sprite[i].sectnum < (unsigned)numsectors
|
|
&& (unsigned)sprite[i].statnum < MAXSTATUS);
|
|
insertsprite(sprite[i].sectnum, sprite[i].statnum);
|
|
}
|
|
|
|
Bassert(Numsprites == mapstate->num[2]);
|
|
|
|
#ifdef POLYMER
|
|
if (in3dmode() && getrendermode() == REND_POLYMER)
|
|
polymer_loadboard();
|
|
#endif
|
|
#ifdef YAX_ENABLE
|
|
yax_update(0);
|
|
yax_updategrays(pos.z);
|
|
#endif
|
|
CheckMapCorruption(4, 0);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
////
|
|
|
|
//// port of a.m32's corruptchk ////
|
|
// returns value from 0 (all OK) to 5 (panic!)
|
|
#define CCHK_PANIC OSDTEXT_DARKRED "PANIC!!!^O "
|
|
//#define CCHKPREF OSDTEXT_RED "^O"
|
|
#define CCHK_CORRECTED OSDTEXT_GREEN " -> "
|
|
|
|
#ifdef _MSC_VER
|
|
#define CORRUPTCHK_PRINT(errlev, what, fmt, ...) do \
|
|
{ \
|
|
bad = max(bad, errlev); \
|
|
if (numcorruptthings>=MAXCORRUPTTHINGS) \
|
|
goto too_many_errors; \
|
|
corruptthings[numcorruptthings++] = (what); \
|
|
if (errlev >= printfromlev) \
|
|
OSD_Printf("#%d: " fmt "\n", numcorruptthings, ## __VA_ARGS__); \
|
|
} while (0)
|
|
#else
|
|
#define CORRUPTCHK_PRINT(errlev, what, fmt, ...) do \
|
|
{ \
|
|
bad = max(bad, errlev); \
|
|
if (numcorruptthings>=MAXCORRUPTTHINGS) \
|
|
goto too_many_errors; \
|
|
corruptthings[numcorruptthings++] = (what); \
|
|
if (errlev >= printfromlev) \
|
|
OSD_Printf_nowarn("#%d: " fmt "\n", numcorruptthings, ## __VA_ARGS__); \
|
|
} while (0)
|
|
#endif
|
|
#ifdef YAX_ENABLE
|
|
static int32_t walls_have_equal_endpoints(int32_t w1, int32_t w2)
|
|
{
|
|
int32_t n1 = wall[w1].point2, n2 = wall[w2].point2;
|
|
|
|
return (wall[w1].x==wall[w2].x && wall[w1].y==wall[w2].y &&
|
|
wall[n1].x==wall[n2].x && wall[n1].y==wall[n2].y);
|
|
}
|
|
|
|
static void correct_yax_nextwall(int32_t wallnum, int32_t bunchnum, int32_t cf, int32_t tryfixingp)
|
|
{
|
|
int32_t i, j, startwall, endwall;
|
|
int32_t nummatching=0, lastwall[2]={-1,-1};
|
|
|
|
for (SECTORS_OF_BUNCH(bunchnum, !cf, i))
|
|
for (WALLS_OF_SECTOR(i, j))
|
|
{
|
|
// v v v shouldn't happen, 'stupidity safety'
|
|
if (j!=wallnum && walls_have_equal_endpoints(wallnum, j))
|
|
{
|
|
lastwall[nummatching++] = j;
|
|
if (nummatching==2)
|
|
goto outofloop;
|
|
}
|
|
}
|
|
outofloop:
|
|
if (nummatching==1)
|
|
{
|
|
if (!tryfixingp)
|
|
{
|
|
OSD_Printf(" will set wall %d's %s to %d on tryfix\n",
|
|
wallnum, yupdownwall[cf], lastwall[0]);
|
|
}
|
|
else
|
|
{
|
|
int32_t setreverse = 0;
|
|
yax_setnextwall(wallnum, cf, lastwall[0]);
|
|
if (yax_getnextwall(lastwall[0], !cf) < 0)
|
|
{
|
|
setreverse = 1;
|
|
yax_setnextwall(lastwall[0], !cf, wallnum);
|
|
}
|
|
|
|
OSD_Printf("auto-correction: set wall %d's %s to %d%s\n",
|
|
wallnum, yupdownwall[cf], lastwall[0], setreverse?" and its reverse link":"");
|
|
}
|
|
}
|
|
else if (!tryfixingp)
|
|
{
|
|
if (nummatching > 1)
|
|
{
|
|
OSD_Printf(" found more than one matching wall: at least %d and %d\n",
|
|
lastwall[0], lastwall[1]);
|
|
}
|
|
else if (nummatching == 0)
|
|
{
|
|
OSD_Printf(" found no matching walls!\n");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// in reverse orientation
|
|
static int32_t walls_are_consistent(int32_t w1, int32_t w2)
|
|
{
|
|
return (wall[w2].x==POINT2(w1).x && wall[w2].y==POINT2(w1).y &&
|
|
wall[w1].x==POINT2(w2).x && wall[w1].y==POINT2(w2).y);
|
|
}
|
|
|
|
static void suggest_nextsector_correction(int32_t nw, int32_t j)
|
|
{
|
|
// wall j's nextsector is inconsistent with its nextwall... what shall we do?
|
|
|
|
if (nw>=0 && nw<numwalls)
|
|
{
|
|
// maybe wall[j].nextwall's nextwall is right?
|
|
if (wall[nw].nextwall==j && walls_are_consistent(nw, j))
|
|
OSD_Printf(" suggest setting wall[%d].nextsector to %d\n",
|
|
j, sectorofwall_noquick(nw));
|
|
}
|
|
|
|
// alternative
|
|
if (wall[j].nextsector>=0 && wall[j].nextsector<numsectors)
|
|
{
|
|
int32_t w, startwall, endwall;
|
|
for (WALLS_OF_SECTOR(wall[j].nextsector, w))
|
|
{
|
|
// XXX: need clearing some others?
|
|
if (walls_are_consistent(w, j))
|
|
{
|
|
OSD_Printf(" ? suggest setting wall[%d].nextwall to %d\n",
|
|
j, w);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
OSD_Printf(" ?? suggest making wall %d white\n", j);
|
|
}
|
|
|
|
static void do_nextsector_correction(int32_t nw, int32_t j)
|
|
{
|
|
if (corrupt_tryfix_alt==0)
|
|
{
|
|
if (nw>=0 && nw<numwalls)
|
|
if (wall[nw].nextwall==j && walls_are_consistent(nw, j))
|
|
{
|
|
int32_t newns = sectorofwall_noquick(nw);
|
|
wall[j].nextsector = newns;
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: set wall[%d].nextsector=%d\n",
|
|
j, newns);
|
|
}
|
|
}
|
|
else if (corrupt_tryfix_alt==1)
|
|
{
|
|
if (wall[j].nextsector>=0 && wall[j].nextsector<numsectors)
|
|
{
|
|
int32_t w, startwall, endwall;
|
|
for (WALLS_OF_SECTOR(wall[j].nextsector, w))
|
|
if (walls_are_consistent(w, j))
|
|
{
|
|
wall[j].nextwall = w;
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: set wall[%d].nextwall=%d\n",
|
|
j, w);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (corrupt_tryfix_alt==2)
|
|
{
|
|
wall[j].nextwall = wall[j].nextsector = -1;
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: made wall %d white\n", j);
|
|
}
|
|
}
|
|
|
|
|
|
static int32_t csc_s, csc_i;
|
|
// 1: corrupt, 0: OK
|
|
static int32_t check_spritelist_consistency()
|
|
{
|
|
int32_t s, i, ournumsprites=0;
|
|
static uint8_t havesprite[MAXSPRITES>>3];
|
|
|
|
csc_s = csc_i = -1;
|
|
|
|
if (Numsprites < 0 || Numsprites > MAXSPRITES)
|
|
return 1;
|
|
|
|
for (i=0; i<MAXSPRITES; i++)
|
|
{
|
|
const int32_t sectnum=sprite[i].sectnum, statnum=sprite[i].statnum;
|
|
|
|
csc_i = i;
|
|
|
|
if ((statnum==MAXSTATUS) != (sectnum==MAXSECTORS))
|
|
return 2; // violation of .statnum==MAXSTATUS iff .sectnum==MAXSECTORS
|
|
|
|
if ((unsigned)statnum > MAXSTATUS || (sectnum!=MAXSECTORS && (unsigned)sectnum > (unsigned)numsectors))
|
|
return 3; // oob sectnum or statnum
|
|
|
|
if (statnum != MAXSTATUS)
|
|
ournumsprites++;
|
|
}
|
|
|
|
if (ournumsprites != Numsprites)
|
|
{
|
|
initprintf("ournumsprites=%d, Numsprites=%d\n", ournumsprites, Numsprites);
|
|
return 4; // counting sprites by statnum!=MAXSTATUS inconsistent with Numsprites
|
|
}
|
|
|
|
// SECTOR LIST
|
|
|
|
Bmemset(havesprite, 0, (Numsprites+7)>>3);
|
|
|
|
for (s=0; s<numsectors; s++)
|
|
{
|
|
csc_s = s;
|
|
|
|
for (i=headspritesect[s]; i>=0; i=nextspritesect[i])
|
|
{
|
|
csc_i = i;
|
|
|
|
if (i >= MAXSPRITES)
|
|
return 5; // oob sprite index in list, or Numsprites inconsistent
|
|
|
|
if (havesprite[i>>3]&(1<<(i&7)))
|
|
return 6; // have a cycle in the list
|
|
|
|
havesprite[i>>3] |= (1<<(i&7));
|
|
|
|
if (sprite[i].sectnum != s)
|
|
return 7; // .sectnum inconsistent with list
|
|
}
|
|
|
|
if (i!=-1)
|
|
return 8; // various code checks for -1 to break loop
|
|
}
|
|
|
|
csc_s = -1;
|
|
for (i=0; i<MAXSPRITES; i++)
|
|
{
|
|
csc_i = i;
|
|
|
|
if (sprite[i].statnum!=MAXSTATUS && !(havesprite[i>>3]&(1<<(i&7))))
|
|
return 9; // have a sprite in the world not in sector list
|
|
}
|
|
|
|
|
|
// STATUS LIST -- we now clear havesprite[] bits
|
|
|
|
for (s=0; s<MAXSTATUS; s++)
|
|
{
|
|
csc_s = s;
|
|
|
|
for (i=headspritestat[s]; i>=0; i=nextspritestat[i])
|
|
{
|
|
csc_i = i;
|
|
|
|
if (i >= MAXSPRITES)
|
|
return 10; // oob sprite index in list, or Numsprites inconsistent
|
|
|
|
// have a cycle in the list, or status list inconsistent with
|
|
// sector list (*)
|
|
if (!(havesprite[i>>3]&(1<<(i&7))))
|
|
return 11;
|
|
|
|
havesprite[i>>3] &= ~(1<<(i&7));
|
|
|
|
if (sprite[i].statnum != s)
|
|
return 12; // .statnum inconsistent with list
|
|
}
|
|
|
|
if (i!=-1)
|
|
return 13; // various code checks for -1 to break loop
|
|
}
|
|
|
|
csc_s = -1;
|
|
for (i=0; i<Numsprites; i++)
|
|
{
|
|
csc_i = i;
|
|
|
|
// Status list contains only a proper subset of the sprites in the
|
|
// sector list. Reverse case is handled by (*)
|
|
if (havesprite[i>>3]&(1<<(i&7)))
|
|
return 14;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define TRYFIX_NONE() (tryfixing == 0ull)
|
|
#define TRYFIX_CNUM(onumct) (onumct < MAXCORRUPTTHINGS && (tryfixing & (1ull<<onumct)))
|
|
|
|
int32_t CheckMapCorruption(int32_t printfromlev, uint64_t tryfixing)
|
|
{
|
|
int32_t i, j, w0, numw, endwall, ns, nw;
|
|
int32_t ewall=0; // expected wall index
|
|
|
|
int32_t errlevel=0, bad=0;
|
|
int32_t heinumcheckstat = 0; // 1, 2
|
|
|
|
uint8_t *seen_nextwalls = NULL;
|
|
int16_t *lastnextwallsource = NULL;
|
|
|
|
numcorruptthings = 0;
|
|
|
|
if (numsectors>MAXSECTORS)
|
|
CORRUPTCHK_PRINT(5, 0, CCHK_PANIC "SECTOR LIMIT EXCEEDED (MAXSECTORS=%d)!!!", MAXSECTORS);
|
|
|
|
if (numwalls>MAXWALLS)
|
|
CORRUPTCHK_PRINT(5, 0, CCHK_PANIC "WALL LIMIT EXCEEDED (MAXWALLS=%d)!!!", MAXWALLS);
|
|
|
|
if (numsectors>MAXSECTORS || numwalls>MAXWALLS)
|
|
{
|
|
corruptlevel = bad;
|
|
return bad;
|
|
}
|
|
|
|
if (numsectors==0 || numwalls==0)
|
|
{
|
|
if (numsectors>0)
|
|
CORRUPTCHK_PRINT(5, 0, CCHK_PANIC " Have sectors but no walls!");
|
|
if (numwalls>0)
|
|
CORRUPTCHK_PRINT(5, 0, CCHK_PANIC " Have walls but no sectors!");
|
|
return bad;
|
|
}
|
|
|
|
if (!corruptcheck_noalreadyrefd)
|
|
{
|
|
seen_nextwalls = (uint8_t *)Xcalloc((numwalls+7)>>3,1);
|
|
lastnextwallsource = (int16_t *)Xmalloc(numwalls*sizeof(lastnextwallsource[0]));
|
|
}
|
|
|
|
for (i=0; i<numsectors; i++)
|
|
{
|
|
bad = 0;
|
|
|
|
w0 = sector[i].wallptr;
|
|
numw = sector[i].wallnum;
|
|
|
|
if (w0 < 0 || w0 > numwalls)
|
|
{
|
|
if (w0 < 0 || w0 >= MAXWALLS)
|
|
CORRUPTCHK_PRINT(5, CORRUPT_SECTOR|i, "SECTOR[%d].WALLPTR=%d INVALID!!!", i, w0);
|
|
else
|
|
CORRUPTCHK_PRINT(5, CORRUPT_SECTOR|i, "SECTOR[%d].WALLPTR=%d out of range (numwalls=%d)", i, w0, numw);
|
|
}
|
|
|
|
if (w0 != ewall)
|
|
CORRUPTCHK_PRINT(4, CORRUPT_SECTOR|i, "SECTOR[%d].WALLPTR=%d inconsistent, expected %d", i, w0, ewall);
|
|
|
|
if (numw <= 1)
|
|
CORRUPTCHK_PRINT(5, CORRUPT_SECTOR|i, CCHK_PANIC "SECTOR[%d].WALLNUM=%d INVALID!!!", i, numw);
|
|
else if (numw==2)
|
|
CORRUPTCHK_PRINT(3, CORRUPT_SECTOR|i, "SECTOR[%d].WALLNUM=2, expected at least 3", i);
|
|
|
|
ewall += numw;
|
|
|
|
endwall = w0 + numw;
|
|
if (endwall > numwalls)
|
|
CORRUPTCHK_PRINT(5, CORRUPT_SECTOR|i, "SECTOR[%d]: wallptr+wallnum=%d out of range: numwalls=%d", i, endwall, numwalls);
|
|
|
|
// inconsistent cstat&2 and heinum checker
|
|
{
|
|
const char *cflabel[2] = {"ceiling", "floor"};
|
|
|
|
for (j=0; j<2; j++)
|
|
{
|
|
const int32_t cs = !!(SECTORFLD(i,stat, j)&2);
|
|
const int32_t hn = !!SECTORFLD(i,heinum, j);
|
|
|
|
if (cs != hn && heinumcheckstat <= 1)
|
|
{
|
|
if (numcorruptthings < MAXCORRUPTTHINGS &&
|
|
(heinumcheckstat==1 || (heinumcheckstat==0 && (tryfixing & (1ull<<numcorruptthings)))))
|
|
{
|
|
setslope(i, j, 0);
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: reset sector %d's %s slope\n",
|
|
i, cflabel[j]);
|
|
heinumcheckstat = 1;
|
|
}
|
|
else if (heinumcheckstat==0)
|
|
{
|
|
CORRUPTCHK_PRINT(1, CORRUPT_SECTOR|i,
|
|
"SECTOR[%d]: inconsistent %sstat&2 and heinum", i, cflabel[j]);
|
|
}
|
|
|
|
if (heinumcheckstat != 1)
|
|
heinumcheckstat = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
errlevel = max(errlevel, bad);
|
|
|
|
if (bad<4)
|
|
{
|
|
endwall--;
|
|
|
|
for (j=w0; j<=endwall; j++)
|
|
{
|
|
bad = 0;
|
|
|
|
if (wall[j].point2 < w0 || wall[j].point2 > endwall)
|
|
{
|
|
if (wall[j].point2 < 0 || wall[j].point2 >= MAXWALLS)
|
|
CORRUPTCHK_PRINT(5, CORRUPT_WALL|j, CCHK_PANIC "WALL[%d].POINT2=%d INVALID!!!",
|
|
j, TrackerCast(wall[j].point2));
|
|
else
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].POINT2=%d out of range [%d, %d]",
|
|
j, TrackerCast(wall[j].point2), w0, endwall);
|
|
}
|
|
|
|
nw = wall[j].nextwall;
|
|
|
|
if (nw >= numwalls)
|
|
{
|
|
int32_t onumct = numcorruptthings;
|
|
|
|
if (TRYFIX_NONE())
|
|
{
|
|
if (nw >= MAXWALLS)
|
|
CORRUPTCHK_PRINT(5, CORRUPT_WALL|j, "WALL[%d].NEXTWALL=%d INVALID!!!",
|
|
j, nw);
|
|
else
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTWALL=%d out of range: numwalls=%d",
|
|
j, nw, numwalls);
|
|
OSD_Printf(" will make wall %d white on tryfix\n", j);
|
|
}
|
|
else if (TRYFIX_CNUM(onumct)) // CODEDUP MAKE_WALL_WHITE
|
|
{
|
|
wall[j].nextwall = wall[j].nextsector = -1;
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: made wall %d white\n", j);
|
|
}
|
|
}
|
|
|
|
ns = wall[j].nextsector;
|
|
|
|
if (ns >= numsectors)
|
|
{
|
|
int32_t onumct = numcorruptthings;
|
|
|
|
if (TRYFIX_NONE())
|
|
{
|
|
if (ns >= MAXSECTORS)
|
|
CORRUPTCHK_PRINT(5, CORRUPT_WALL|j, "WALL[%d].NEXTSECTOR=%d INVALID!!!",
|
|
j, ns);
|
|
else
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTSECTOR=%d out of range: numsectors=%d",
|
|
j, ns, numsectors);
|
|
OSD_Printf(" will make wall %d white on tryfix\n", j);
|
|
}
|
|
else if (TRYFIX_CNUM(onumct)) // CODEDUP MAKE_WALL_WHITE
|
|
{
|
|
wall[j].nextwall = wall[j].nextsector = -1;
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: made wall %d white\n", j);
|
|
}
|
|
}
|
|
|
|
if (nw>=w0 && nw<=endwall)
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTWALL is its own sector's wall", j);
|
|
|
|
if (wall[j].x==POINT2(j).x && wall[j].y==POINT2(j).y)
|
|
CORRUPTCHK_PRINT(3, CORRUPT_WALL|j, "WALL[%d] has length 0", j);
|
|
|
|
#ifdef YAX_ENABLE
|
|
{
|
|
int32_t cf, ynw, ynwp2;
|
|
|
|
for (cf=0; cf<2; cf++)
|
|
{
|
|
ynw = yax_getnextwall(j, cf);
|
|
if (ynw >= 0)
|
|
{
|
|
if (ynw >= numwalls)
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d's %s=%d out of range: numwalls=%d",
|
|
j, YUPDOWNWALL[cf], ynw, numwalls);
|
|
else
|
|
{
|
|
int32_t ynextwallok = 1;
|
|
|
|
if (j == ynw)
|
|
{
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d's %s is itself",
|
|
j, YUPDOWNWALL[cf]);
|
|
ynextwallok = 0;
|
|
}
|
|
else if (!walls_have_equal_endpoints(j, ynw))
|
|
{
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d's and its %s=%d's "
|
|
"endpoints are inconsistent", j, YUPDOWNWALL[cf], ynw);
|
|
ynextwallok = 0;
|
|
}
|
|
|
|
{
|
|
int16_t bunchnum = yax_getbunch(i, cf);
|
|
int32_t onumct = numcorruptthings;
|
|
|
|
if (bunchnum < 0 || bunchnum >= numyaxbunches)
|
|
{
|
|
if (tryfixing == 0ull)
|
|
{
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d has %s=%d, "
|
|
"but its %s bunchnum=%d is invalid",
|
|
j, YUPDOWNWALL[cf], ynw,
|
|
cf==YAX_CEILING? "ceiling":"floor", bunchnum);
|
|
OSD_Printf(" will clear wall %d's %s to -1 on tryfix\n",
|
|
j, yupdownwall[cf]);
|
|
|
|
}
|
|
else if (tryfixing & (1ull<<onumct))
|
|
{
|
|
yax_setnextwall(j, cf, -1);
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: cleared wall %d's %s to -1\n",
|
|
j, yupdownwall[cf]);
|
|
}
|
|
}
|
|
else if (!ynextwallok && onumct < MAXCORRUPTTHINGS)
|
|
{
|
|
if ((tryfixing & (1ull<<onumct)) || 4>=printfromlev)
|
|
correct_yax_nextwall(j, bunchnum, cf, tryfixing!=0ull);
|
|
}
|
|
}
|
|
|
|
if (ynextwallok)
|
|
{
|
|
int32_t onumct = numcorruptthings;
|
|
|
|
ynwp2 = yax_getnextwall(ynw, !cf);
|
|
if (ynwp2 != j)
|
|
{
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d's %s=%d's reverse link wrong"
|
|
" (expected %d, have %d)", j, YUPDOWNWALL[cf], ynw, j, ynwp2);
|
|
if (onumct < MAXCORRUPTTHINGS)
|
|
{
|
|
if (tryfixing & (1ull<<onumct))
|
|
{
|
|
yax_setnextwall(ynw, !cf, j);
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: set wall %d's %s=%d's %s to %d\n",
|
|
j, yupdownwall[cf], ynw, yupdownwall[!cf], j);
|
|
}
|
|
else if (4>=printfromlev)
|
|
{
|
|
OSD_Printf(" will set wall %d's %s=%d's %s to %d on tryfix\n",
|
|
j, yupdownwall[cf], ynw, yupdownwall[!cf], j);
|
|
}
|
|
}
|
|
}
|
|
} // woot!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (ns == i)
|
|
{
|
|
if (!bad)
|
|
{
|
|
int32_t onumct = numcorruptthings;
|
|
int32_t safetoclear = (nw==j || (wall[nw].nextwall==-1 && wall[nw].nextsector==-1));
|
|
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTSECTOR is its own sector", j);
|
|
if (onumct < MAXCORRUPTTHINGS)
|
|
{
|
|
if (tryfixing & (1ull<<onumct))
|
|
{
|
|
if (safetoclear)
|
|
{
|
|
wall[j].nextwall = wall[j].nextsector = -1;
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: cleared wall %d's nextwall"
|
|
" and nextsector\n", j);
|
|
}
|
|
else
|
|
do_nextsector_correction(nw, j);
|
|
}
|
|
else if (4>=printfromlev)
|
|
{
|
|
if (safetoclear)
|
|
OSD_Printf(" will clear wall %d's nextwall and nextsector on tryfix\n", j);
|
|
else
|
|
suggest_nextsector_correction(nw, j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!corruptcheck_noalreadyrefd && nw>=0 && nw<numwalls)
|
|
{
|
|
if (seen_nextwalls[nw>>3]&(1<<(nw&7)))
|
|
{
|
|
int16_t nwnw, lnws;
|
|
int32_t onumct = numcorruptthings;
|
|
|
|
lnws = lastnextwallsource[nw];
|
|
CORRUPTCHK_PRINT(3, CORRUPT_WALL|j, "WALL[%d].NEXTWALL=%d already referenced from wall %d",
|
|
j, nw, lnws);
|
|
nwnw = wall[nw].nextwall;
|
|
if (onumct < MAXCORRUPTTHINGS && (nwnw==j || nwnw==lnws))
|
|
{
|
|
int32_t walltoclear = nwnw==j ? lnws : j;
|
|
if (tryfixing & (1ull<<onumct))
|
|
{
|
|
wall[walltoclear].nextsector = wall[walltoclear].nextwall = -1;
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: cleared wall %d's nextwall and nextsector tags to -1\n",
|
|
walltoclear);
|
|
}
|
|
else if (3 >= printfromlev)
|
|
OSD_Printf(" wall[%d].nextwall=%d, suggest clearing wall %d's nextwall and nextsector tags to -1\n",
|
|
nw, nwnw, walltoclear);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
seen_nextwalls[nw>>3] |= 1<<(nw&7);
|
|
lastnextwallsource[nw] = j;
|
|
}
|
|
}
|
|
|
|
if (bad<4)
|
|
{
|
|
int32_t onumct = numcorruptthings;
|
|
if ((ns^nw)<0)
|
|
{
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTSECTOR=%d and .NEXTWALL=%d inconsistent:"
|
|
" missing one next pointer", j, ns, nw);
|
|
if (onumct < MAXCORRUPTTHINGS)
|
|
{
|
|
if (tryfixing & (1ull<<onumct))
|
|
do_nextsector_correction(nw, j);
|
|
else if (4>=printfromlev)
|
|
suggest_nextsector_correction(nw, j);
|
|
}
|
|
}
|
|
else if (ns>=0)
|
|
{
|
|
if (nw<sector[ns].wallptr || nw>=sector[ns].wallptr+sector[ns].wallnum)
|
|
{
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTWALL=%d out of .NEXTSECTOR=%d's bounds [%d .. %d]",
|
|
j, nw, ns, TrackerCast(sector[ns].wallptr), sector[ns].wallptr+sector[ns].wallnum-1);
|
|
if (onumct < MAXCORRUPTTHINGS)
|
|
{
|
|
if (tryfixing & (1ull<<onumct))
|
|
do_nextsector_correction(nw, j);
|
|
else if (4 >= printfromlev)
|
|
suggest_nextsector_correction(nw, j);
|
|
}
|
|
}
|
|
#if 0
|
|
// this one usually appears together with the "already referenced" corruption
|
|
else if (wall[nw].nextsector != i || wall[nw].nextwall != j)
|
|
{
|
|
CORRUPTCHK_PRINT(4, CORRUPT_WALL|nw, "WALL %d nextwall's backreferences inconsistent. Expected nw=%d, ns=%d; got nw=%d, ns=%d",
|
|
nw, i, j, wall[nw].nextsector, wall[nw].nextwall);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
errlevel = max(errlevel, bad);
|
|
}
|
|
}
|
|
}
|
|
|
|
bad = 0;
|
|
for (i=0; i<MAXSPRITES; i++)
|
|
{
|
|
if (sprite[i].statnum==MAXSTATUS)
|
|
continue;
|
|
|
|
if (sprite[i].sectnum<0 || sprite[i].sectnum>=numsectors)
|
|
CORRUPTCHK_PRINT(4, CORRUPT_SPRITE|i, "SPRITE[%d].SECTNUM=%d. Expect problems!", i, TrackerCast(sprite[i].sectnum));
|
|
|
|
if (sprite[i].statnum<0 || sprite[i].statnum>MAXSTATUS)
|
|
CORRUPTCHK_PRINT(4, CORRUPT_SPRITE|i, "SPRITE[%d].STATNUM=%d. Expect problems!", i, TrackerCast(sprite[i].statnum));
|
|
|
|
if (sprite[i].picnum<0 || sprite[i].picnum>=MAXTILES)
|
|
{
|
|
sprite[i].picnum = 0;
|
|
CORRUPTCHK_PRINT(0, CORRUPT_SPRITE|i, "SPRITE[%d].PICNUM=%d out of range, resetting to 0", i, TrackerCast(sprite[i].picnum));
|
|
}
|
|
|
|
if (klabs(sprite[i].x) > BXY_MAX || klabs(sprite[i].y) > BXY_MAX)
|
|
{
|
|
int32_t onumct = numcorruptthings;
|
|
|
|
CORRUPTCHK_PRINT(3, CORRUPT_SPRITE|i, "SPRITE %d at [%d, %d] is outside the maximal grid range [%d, %d]",
|
|
i, TrackerCast(sprite[i].x), TrackerCast(sprite[i].y), -BXY_MAX, BXY_MAX);
|
|
|
|
if (onumct < MAXCORRUPTTHINGS)
|
|
{
|
|
int32_t x=0, y=0, sect=sprite[i].sectnum, ok=0;
|
|
|
|
if ((unsigned)sect < (unsigned)numsectors)
|
|
{
|
|
int32_t firstwall = sector[sect].wallptr;
|
|
|
|
if ((unsigned)firstwall < (unsigned)numwalls)
|
|
{
|
|
x = wall[firstwall].x;
|
|
y = wall[firstwall].y;
|
|
ok = 1;
|
|
}
|
|
}
|
|
|
|
if (!(tryfixing & (1ull<<onumct)))
|
|
{
|
|
if (ok && 3 >= printfromlev)
|
|
OSD_Printf(" will reposition to its sector's (%d) first"
|
|
" point [%d,%d] on tryfix\n", sect, x, y);
|
|
}
|
|
else
|
|
{
|
|
if (ok)
|
|
{
|
|
sprite[i].x = x;
|
|
sprite[i].y = y;
|
|
OSD_Printf(CCHK_CORRECTED "auto-correction: repositioned sprite %d to "
|
|
"its sector's (%d) first point [%d,%d]\n", i, sect, x, y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
i = check_spritelist_consistency();
|
|
if (i)
|
|
CORRUPTCHK_PRINT(5, i<0?0:(CORRUPT_SPRITE|i), CCHK_PANIC "SPRITE LISTS CORRUPTED: error code %d, s=%d, i=%d!",
|
|
i, csc_s, csc_i);
|
|
|
|
if (0)
|
|
{
|
|
too_many_errors:
|
|
if (printfromlev<=errlevel)
|
|
OSD_Printf("!! too many errors, stopping. !!\n");
|
|
}
|
|
|
|
errlevel = max(errlevel, bad);
|
|
|
|
if (errlevel)
|
|
{
|
|
if (printfromlev<=errlevel)
|
|
OSD_Printf("-- corruption level: %d\n", errlevel);
|
|
if (tryfixing)
|
|
OSD_Printf("--\n");
|
|
}
|
|
|
|
if (seen_nextwalls)
|
|
{
|
|
Bfree(seen_nextwalls);
|
|
Bfree(lastnextwallsource);
|
|
}
|
|
|
|
corruptlevel = errlevel;
|
|
|
|
return errlevel;
|
|
}
|
|
////
|
|
|
|
|
|
////////// STATUS BAR MENU "class" //////////
|
|
|
|
#define MENU_MAX_ENTRIES (8*3)
|
|
#define MENU_ENTRY_SIZE 25 // max. length of label (including terminating NUL)
|
|
|
|
#define MENU_Y_SPACING 8
|
|
#define MENU_BASE_Y (ydim-STATUS2DSIZ+32)
|
|
|
|
#define MENU_FG_COLOR editorcolors[11]
|
|
#define MENU_BG_COLOR editorcolors[0]
|
|
#define MENU_BG_COLOR_SEL editorcolors[1]
|
|
|
|
#ifdef LUNATIC
|
|
# define MENU_HAVE_DESCRIPTION 1
|
|
#else
|
|
# define MENU_HAVE_DESCRIPTION 0
|
|
#endif
|
|
|
|
typedef struct StatusBarMenu_ {
|
|
const char *const menuname;
|
|
const int32_t custom_start_index;
|
|
int32_t numentries;
|
|
|
|
void (*process_func)(const struct StatusBarMenu_ *m, int32_t col, int32_t row);
|
|
|
|
intptr_t auxdata[MENU_MAX_ENTRIES];
|
|
char *description[MENU_MAX_ENTRIES]; // strdup'd description string, NULL if non
|
|
char name[MENU_MAX_ENTRIES][MENU_ENTRY_SIZE];
|
|
} StatusBarMenu;
|
|
|
|
#define MENU_INITIALIZER_EMPTY(MenuName, ProcessFunc) \
|
|
{ MenuName, 0, 0, ProcessFunc, {}, {}, {} }
|
|
#define MENU_INITIALIZER(MenuName, CustomStartIndex, ProcessFunc, ...) \
|
|
{ MenuName, CustomStartIndex, CustomStartIndex, ProcessFunc, {}, {}, ## __VA_ARGS__ }
|
|
|
|
#ifdef LUNATIC
|
|
static void M_Clear(StatusBarMenu *m)
|
|
{
|
|
int32_t i;
|
|
|
|
m->numentries = 0;
|
|
Bmemset(m->auxdata, 0, sizeof(m->auxdata));
|
|
Bmemset(m->name, 0, sizeof(m->name));
|
|
|
|
for (i=0; i<MENU_MAX_ENTRIES; i++)
|
|
{
|
|
Bfree(m->description[i]);
|
|
m->description[i] = NULL;
|
|
}
|
|
}
|
|
|
|
static int32_t M_HaveDescription(StatusBarMenu *m)
|
|
{
|
|
int32_t i;
|
|
|
|
for (i=0; i<MENU_MAX_ENTRIES; i++)
|
|
if (m->description[i] != NULL)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
// NOTE: Does not handle description strings! (Only the Lua menu uses them.)
|
|
static void M_UnregisterFunction(StatusBarMenu *m, intptr_t auxdata)
|
|
{
|
|
int32_t i, j;
|
|
|
|
for (i=m->custom_start_index; i<m->numentries; i++)
|
|
if (m->auxdata[i]==auxdata)
|
|
{
|
|
for (j=i; j<m->numentries-1; j++)
|
|
{
|
|
m->auxdata[j] = m->auxdata[j+1];
|
|
Bmemcpy(m->name[j], m->name[j+1], MENU_ENTRY_SIZE);
|
|
}
|
|
|
|
m->auxdata[j] = 0;
|
|
Bmemset(m->name[j], 0, MENU_ENTRY_SIZE);
|
|
|
|
m->numentries--;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void M_RegisterFunction(StatusBarMenu *m, const char *name, intptr_t auxdata, const char *description)
|
|
{
|
|
int32_t i;
|
|
|
|
for (i=8; i<m->numentries; i++)
|
|
{
|
|
if (m->auxdata[i]==auxdata)
|
|
{
|
|
// same auxdata, different name
|
|
Bstrncpyz(m->name[i], name, MENU_ENTRY_SIZE);
|
|
return;
|
|
}
|
|
else if (!Bstrncmp(m->name[i], name, MENU_ENTRY_SIZE))
|
|
{
|
|
// same name, different auxdata
|
|
m->auxdata[i] = auxdata;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (m->numentries == MENU_MAX_ENTRIES)
|
|
return; // max reached
|
|
|
|
Bstrncpyz(m->name[m->numentries], name, MENU_ENTRY_SIZE);
|
|
m->auxdata[m->numentries] = auxdata;
|
|
|
|
#if MENU_HAVE_DESCRIPTION
|
|
// NOTE: description only handled here (not above).
|
|
if (description)
|
|
m->description[m->numentries] = Xstrdup(description);
|
|
#else
|
|
UNREFERENCED_PARAMETER(description);
|
|
#endif
|
|
|
|
m->numentries++;
|
|
}
|
|
|
|
static void M_DisplayInitial(const StatusBarMenu *m)
|
|
{
|
|
int32_t x = 8, y = MENU_BASE_Y+16;
|
|
int32_t i;
|
|
|
|
for (i=0; i<m->numentries; i++)
|
|
{
|
|
if (i==8 || i==16)
|
|
{
|
|
x += 208;
|
|
y = MENU_BASE_Y+16;
|
|
}
|
|
|
|
printext16(x,y, MENU_FG_COLOR, MENU_BG_COLOR, m->name[i], 0);
|
|
y += MENU_Y_SPACING;
|
|
}
|
|
|
|
printext16(m->numentries>8 ? 216 : 8, MENU_BASE_Y, MENU_FG_COLOR, -1, m->menuname, 0);
|
|
|
|
clearkeys();
|
|
}
|
|
|
|
static void M_EnterMainLoop(StatusBarMenu *m)
|
|
{
|
|
char disptext[80];
|
|
const int32_t dispwidth = MENU_ENTRY_SIZE-1;
|
|
|
|
int32_t i, col=0, row=0;
|
|
int32_t crowmax[3] = {-1, -1, -1};
|
|
int32_t xpos = 8, ypos = MENU_BASE_Y+16;
|
|
|
|
if (m->numentries == 0)
|
|
{
|
|
printmessage16("%s menu has no entries", m->menuname);
|
|
return;
|
|
}
|
|
|
|
Bmemset(disptext, 0, sizeof(disptext));
|
|
|
|
Bassert((unsigned)m->numentries <= MENU_MAX_ENTRIES);
|
|
for (i=0; i<=(m->numentries-1)/8; i++)
|
|
crowmax[i] = (m->numentries >= (i+1)*8) ? 7 : (m->numentries-1)&7;
|
|
|
|
drawgradient();
|
|
M_DisplayInitial(m);
|
|
|
|
while (keystatus[KEYSC_ESC] == 0)
|
|
{
|
|
idle_waitevent();
|
|
if (handleevents())
|
|
quitevent = 0;
|
|
|
|
_printmessage16("Select an option, press <Esc> to exit");
|
|
|
|
if (PRESSED_KEYSC(DOWN))
|
|
{
|
|
if (row < crowmax[col])
|
|
{
|
|
printext16(xpos, ypos+row*MENU_Y_SPACING, MENU_FG_COLOR, MENU_BG_COLOR, disptext, 0);
|
|
row++;
|
|
}
|
|
}
|
|
else if (PRESSED_KEYSC(UP))
|
|
{
|
|
if (row > 0)
|
|
{
|
|
printext16(xpos, ypos+row*MENU_Y_SPACING, MENU_FG_COLOR, MENU_BG_COLOR, disptext, 0);
|
|
row--;
|
|
}
|
|
}
|
|
else if (PRESSED_KEYSC(LEFT))
|
|
{
|
|
if (col > 0)
|
|
{
|
|
printext16(xpos, ypos+row*8, MENU_FG_COLOR, 0, disptext, 0);
|
|
col--;
|
|
xpos -= 208;
|
|
disptext[dispwidth] = 0;
|
|
row = min(crowmax[col], row);
|
|
}
|
|
}
|
|
else if (PRESSED_KEYSC(RIGHT))
|
|
{
|
|
if (col < 2 && crowmax[col+1]>=0)
|
|
{
|
|
printext16(xpos, ypos+row*8, MENU_FG_COLOR, 0, disptext, 0);
|
|
col++;
|
|
xpos += 208;
|
|
disptext[dispwidth] = 0;
|
|
row = min(crowmax[col], row);
|
|
}
|
|
}
|
|
|
|
for (i=Bsnprintf(disptext, dispwidth, "%s", m->name[col*8 + row]); i < dispwidth; i++)
|
|
disptext[i] = ' ';
|
|
|
|
if (PRESSED_KEYSC(ENTER))
|
|
{
|
|
Bassert(m->process_func != NULL);
|
|
m->process_func(m, col, row);
|
|
break;
|
|
}
|
|
|
|
#if MENU_HAVE_DESCRIPTION
|
|
if (M_HaveDescription(m))
|
|
{
|
|
const int32_t maxrows = 20;
|
|
int32_t r;
|
|
|
|
for (r=0; r<maxrows+1; r++)
|
|
printext16(16-4, 16-4 + r*8, 0, MENU_BG_COLOR, // 71 blanks:
|
|
" ", 0);
|
|
if (m->description[col*8 + row] != NULL)
|
|
printext16(16, 16, MENU_FG_COLOR, MENU_BG_COLOR, m->description[col*8 + row], 0);
|
|
}
|
|
#endif
|
|
printext16(xpos, ypos+row*MENU_Y_SPACING, MENU_FG_COLOR, MENU_BG_COLOR_SEL, disptext, 0);
|
|
showframe(1);
|
|
}
|
|
|
|
printext16(xpos, ypos+row*MENU_Y_SPACING, MENU_FG_COLOR, MENU_BG_COLOR, disptext, 0);
|
|
showframe(1);
|
|
|
|
keystatus[KEYSC_ESC] = 0;
|
|
}
|
|
|
|
////////// SPECIAL FUNCTIONS MENU //////////
|
|
|
|
static void FuncMenu_Process(const StatusBarMenu *m, int32_t col, int32_t row);
|
|
|
|
static StatusBarMenu g_specialFuncMenu = MENU_INITIALIZER(
|
|
"Special functions", 8, FuncMenu_Process,
|
|
|
|
{
|
|
"Replace invalid tiles",
|
|
"Delete all spr of tile #",
|
|
"Set map sky shade",
|
|
"Set map sky height",
|
|
"Global Z coord shift",
|
|
"Resize selection",
|
|
"Global shade divide",
|
|
"Global visibility divide"
|
|
}
|
|
);
|
|
|
|
// "External functions":
|
|
|
|
void FuncMenu(void)
|
|
{
|
|
M_EnterMainLoop(&g_specialFuncMenu);
|
|
}
|
|
|
|
void registerMenuFunction(const char *funcname, int32_t stateidx)
|
|
{
|
|
if (funcname)
|
|
M_RegisterFunction(&g_specialFuncMenu, funcname, stateidx, NULL);
|
|
else
|
|
M_UnregisterFunction(&g_specialFuncMenu, stateidx);
|
|
}
|
|
|
|
// The processing function...
|
|
|
|
static int32_t correct_picnum(int16_t *picnumptr)
|
|
{
|
|
int32_t picnum = *picnumptr;
|
|
|
|
if ((unsigned)picnum >= MAXTILES || tilesizx[picnum] <= 0)
|
|
{
|
|
*picnumptr = 0;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void FuncMenu_Process(const StatusBarMenu *m, int32_t col, int32_t row)
|
|
{
|
|
int32_t i, j;
|
|
|
|
switch (col)
|
|
{
|
|
|
|
case 1:
|
|
case 2:
|
|
{
|
|
const int32_t stateidx = (Bassert(m->auxdata[col*8 + row] < g_stateCount),
|
|
m->auxdata[col*8 + row]);
|
|
|
|
const char *statename = statesinfo[stateidx].name;
|
|
int32_t snlen = Bstrlen(statename);
|
|
char *tmpscript = (char *)Xmalloc(1+5+1+snlen+1);
|
|
|
|
tmpscript[0] = ' '; // don't save in history
|
|
Bmemcpy(&tmpscript[1], "state", 5);
|
|
tmpscript[1+5] = ' ';
|
|
Bmemcpy(&tmpscript[1+5+1], statename, snlen);
|
|
tmpscript[1+5+1+snlen] = 0;
|
|
|
|
M32RunScript(tmpscript);
|
|
Bfree(tmpscript);
|
|
|
|
if (vm.flags&VMFLAG_ERROR)
|
|
printmessage16("There were errors while executing the menu function");
|
|
else if (lastpm16time != totalclock)
|
|
printmessage16("Menu function executed successfully");
|
|
}
|
|
break;
|
|
|
|
case 0:
|
|
{
|
|
switch (row)
|
|
{
|
|
|
|
case 0:
|
|
{
|
|
j = 0;
|
|
|
|
for (i=0; i<MAXSECTORS; i++)
|
|
{
|
|
j += correct_picnum(§or[i].ceilingpicnum);
|
|
j += correct_picnum(§or[i].floorpicnum);
|
|
}
|
|
|
|
for (i=0; i<MAXWALLS; i++)
|
|
{
|
|
j += correct_picnum(&wall[i].picnum);
|
|
j += correct_picnum(&wall[i].overpicnum);
|
|
}
|
|
for (i=0; i<MAXSPRITES; i++)
|
|
j += correct_picnum(&sprite[i].picnum);
|
|
|
|
printmessage16("Replaced %d invalid tiles",j);
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
char tempbuf[64];
|
|
Bsprintf(tempbuf,"Delete all sprites of tile #: ");
|
|
i = getnumber16(tempbuf,-1,MAXSPRITES-1,1);
|
|
if (i >= 0)
|
|
{
|
|
int32_t k = 0;
|
|
for (j=0; j<MAXSPRITES; j++)
|
|
if (sprite[j].picnum == i)
|
|
deletesprite(j), k++;
|
|
printmessage16("%d sprite(s) deleted",k);
|
|
}
|
|
else printmessage16("Aborted");
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
{
|
|
j=getnumber16("Set map sky shade: ",0,128,1);
|
|
|
|
for (i=0; i<numsectors; i++)
|
|
{
|
|
if (sector[i].ceilingstat&1)
|
|
sector[i].ceilingshade = j;
|
|
}
|
|
printmessage16("All parallax skies adjusted");
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
{
|
|
j=getnumber16("Set map sky height: ",0,16777216,1);
|
|
if (j != 0)
|
|
{
|
|
for (i=0; i<numsectors; i++)
|
|
{
|
|
if (sector[i].ceilingstat&1)
|
|
sector[i].ceilingz = j;
|
|
}
|
|
printmessage16("All parallax skies adjusted");
|
|
}
|
|
else printmessage16("Aborted");
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
{
|
|
j=getnumber16("Z offset: ",0,16777216,1);
|
|
if (j!=0)
|
|
{
|
|
for (i=0; i<numsectors; i++)
|
|
{
|
|
sector[i].ceilingz += j;
|
|
sector[i].floorz += j;
|
|
}
|
|
for (i=0; i<MAXSPRITES; i++)
|
|
sprite[i].z += j;
|
|
printmessage16("Map adjusted");
|
|
}
|
|
else printmessage16("Aborted");
|
|
}
|
|
break;
|
|
|
|
case 5:
|
|
{
|
|
j=getnumber16("Percentage of original: ",100,1000,0);
|
|
|
|
if (j!=100 && j!=0)
|
|
{
|
|
int32_t w, currsector, start_wall, end_wall;
|
|
double size = (j/100.f);
|
|
|
|
for (i = 0; i < highlightsectorcnt; i++)
|
|
{
|
|
currsector = highlightsector[i];
|
|
sector[currsector].ceilingz = (int32_t)(sector[currsector].ceilingz*size);
|
|
sector[currsector].floorz = (int32_t)(sector[currsector].floorz*size);
|
|
|
|
// Do all the walls in the sector
|
|
start_wall = sector[currsector].wallptr;
|
|
end_wall = start_wall + sector[currsector].wallnum;
|
|
|
|
for (w = start_wall; w < end_wall; w++)
|
|
{
|
|
wall[w].x = (int32_t)(wall[w].x*size);
|
|
wall[w].y = (int32_t)(wall[w].y*size);
|
|
wall[w].yrepeat = min((int32_t)(wall[w].yrepeat/size),255);
|
|
}
|
|
|
|
w = headspritesect[highlightsector[i]];
|
|
while (w >= 0)
|
|
{
|
|
sprite[w].x = (int32_t)(sprite[w].x*size);
|
|
sprite[w].y = (int32_t)(sprite[w].y*size);
|
|
sprite[w].z = (int32_t)(sprite[w].z*size);
|
|
sprite[w].xrepeat = min(max((int32_t)(sprite[w].xrepeat*size),1),255);
|
|
sprite[w].yrepeat = min(max((int32_t)(sprite[w].yrepeat*size),1),255);
|
|
w = nextspritesect[w];
|
|
}
|
|
}
|
|
printmessage16("Map scaled");
|
|
}
|
|
else printmessage16("Aborted");
|
|
}
|
|
break;
|
|
|
|
case 6:
|
|
{
|
|
j=getnumber16("Shade divisor: ",1,128,1);
|
|
if (j > 1)
|
|
{
|
|
for (i=0; i<numsectors; i++)
|
|
{
|
|
sector[i].ceilingshade /= j;
|
|
sector[i].floorshade /= j;
|
|
}
|
|
for (i=0; i<numwalls; i++)
|
|
wall[i].shade /= j;
|
|
for (i=0; i<MAXSPRITES; i++)
|
|
sprite[i].shade /= j;
|
|
printmessage16("Shades adjusted");
|
|
}
|
|
else printmessage16("Aborted");
|
|
}
|
|
break;
|
|
|
|
case 7:
|
|
{
|
|
j=getnumber16("Visibility divisor: ",1,128,0);
|
|
if (j > 1)
|
|
{
|
|
for (i=0; i<numsectors; i++)
|
|
{
|
|
if (sector[i].visibility < 240)
|
|
sector[i].visibility /= j;
|
|
else sector[i].visibility = 240 + (sector[i].visibility>>4)/j;
|
|
}
|
|
printmessage16("Visibility adjusted");
|
|
}
|
|
else printmessage16("Aborted");
|
|
}
|
|
break;
|
|
|
|
} // switch (row)
|
|
}
|
|
break; // switch (col) / case 0
|
|
|
|
} // switch (col)
|
|
}
|
|
|
|
#ifdef LUNATIC
|
|
typedef const char *(*luamenufunc_t)(void);
|
|
|
|
static int32_t g_numLuaFuncs = 0;
|
|
static luamenufunc_t g_LuaFuncPtrs[MENU_MAX_ENTRIES];
|
|
|
|
static void LuaFuncMenu_Process(const StatusBarMenu *m, int32_t col, int32_t row)
|
|
{
|
|
luamenufunc_t func = g_LuaFuncPtrs[col*8 + row];
|
|
const char *errmsg;
|
|
|
|
Bassert(func != NULL);
|
|
errmsg = func();
|
|
|
|
if (errmsg == NULL)
|
|
{
|
|
printmessage16("Lua function executed successfully");
|
|
}
|
|
else
|
|
{
|
|
printmessage16("There were errors executing the Lua function, see OSD");
|
|
OSD_Printf("Errors executing Lua function \"%s\": %s\n", m->name[col*8 + row], errmsg);
|
|
}
|
|
}
|
|
|
|
static StatusBarMenu g_LuaFuncMenu = MENU_INITIALIZER_EMPTY("Lua functions", LuaFuncMenu_Process);
|
|
|
|
void LuaFuncMenu(void)
|
|
{
|
|
M_EnterMainLoop(&g_LuaFuncMenu);
|
|
}
|
|
|
|
LUNATIC_EXTERN void LM_Register(const char *name, luamenufunc_t funcptr, const char *description)
|
|
{
|
|
if (name == NULL || g_numLuaFuncs == MENU_MAX_ENTRIES)
|
|
return;
|
|
|
|
g_LuaFuncPtrs[g_numLuaFuncs] = funcptr;
|
|
M_RegisterFunction(&g_LuaFuncMenu, name, g_numLuaFuncs, description);
|
|
g_numLuaFuncs++;
|
|
}
|
|
|
|
LUNATIC_EXTERN void LM_Clear(void)
|
|
{
|
|
M_Clear(&g_LuaFuncMenu);
|
|
|
|
g_numLuaFuncs = 0;
|
|
Bmemset(g_LuaFuncPtrs, 0, sizeof(g_LuaFuncPtrs));
|
|
}
|
|
#endif
|