// This object is shared by the editors of *all* Build games! #include "compat.h" #include "keys.h" #include "build.h" #include "cache1d.h" #ifdef POLYMER # include "polymer.h" #endif #include "editor.h" #include "renderlayer.h" #include "m32script.h" #include "m32def.h" #include "lz4.h" #include "xxhash.h" // XXX: This breaks editors for games other than Duke. The OSD needs a way to specify colors in abstract instead of concatenating palswap escape sequences. #include "common_game.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, corruptcheck_heinum=1; int32_t corruptcheck_game_duke3d=1; // TODO: at startup, make conditional on which game we are editing for? 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 = editorcolors[25]; videoBeginDrawing(); for (i=ydim-STATUS2DSIZ+16; i0; i++,col--) CLEARLINES2D(i, 1, (col<<24)|(col<<16)|(col<<8)|col); CLEARLINES2D(i, ydim-i, 0); videoEndDrawing(); } 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++) DO_FREE_AND_NULL(g_taglab.label[i]); } 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 DO_FREE_AND_NULL(g_taglab.label[tag]); 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') DO_FREE_AND_NULL(g_taglab.label[olabeltag]); 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; static int32_t try_match_with_prev(int32_t idx, int32_t numsthgs, uintptr_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, uintptr_t crc) { uint32_t j; // allocate int const compressed_size = LZ4_compressBound(size); mapstate->sws[idx] = (char *)Xmalloc(4 + compressed_size); // compress & realloc j = LZ4_compress_default((const char*)srcdata, mapstate->sws[idx]+4, size, compressed_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) { #if !defined UINTPTR_MAX # error Need UINTPTR_MAX define to select between 32- and 64-bit functions #endif #if UINTPTR_MAX == 0xffffffff /* 32-bit */ #define XXH__ XXH32 #else /* 64-bit */ #define XXH__ XXH64 #endif uintptr_t temphash = XXH__((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 = XXH__((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 = XXH__((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 (bssize_t j=0; jnext == 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; inum[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 //// // Compile wall loop checks? 0: no, 1: partial, 2: full. #define CCHK_LOOP_CHECKS 0 // 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 " -> " #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) #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=0 && wall[j].nextsector=0 && nw=0 && wall[j].nextsector>3]; csc_s = csc_i = -1; if (Numsprites < 0 || Numsprites > MAXSPRITES) return 1; for (bssize_t i=0; i 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 (bssize_t s=0; s=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 (bssize_t i=0; 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 (bssize_t s=0; s=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 (bssize_t i=0; i>3]&(1<<(i&7))) return 14; } return 0; } #if CCHK_LOOP_CHECKS // Return the least wall index of the outer loop of sector , or // -1 if there is none, // -2 if there is more than one. static int32_t determine_outer_loop(int32_t sectnum) { int32_t j, outerloopstart = -1; const int32_t startwall = sector[sectnum].wallptr; const int32_t endwall = startwall + sector[sectnum].wallnum - 1; for (j=startwall; j<=endwall; j=get_nextloopstart(j)) { if (clockdir(j) == CLOCKDIR_CW) { if (outerloopstart == -1) outerloopstart = j; else if (outerloopstart >= 0) return -2; } } return outerloopstart; } #endif #define TRYFIX_NONE() (tryfixing == 0ull) #define TRYFIX_CNUM(onumct) (onumct < MAXCORRUPTTHINGS && (tryfixing & (1ull<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 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; 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 if (corruptcheck_heinum) { 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< 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); } if (nw >= numwalls) { const 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); } } if (ns >= numsectors) { const 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 // Various TROR checks. { int32_t cf; for (cf=0; cf<2; cf++) { const int32_t 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; } { const int16_t bunchnum = yax_getbunch(i, cf); const 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<=printfromlev) correct_yax_nextwall(j, bunchnum, cf, tryfixing!=0ull); } } if (ynextwallok) { const int32_t onumct = numcorruptthings; const int32_t 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<=printfromlev) { OSD_Printf(" will set wall %d's %s=%d's %s to %d on tryfix\n", j, yupdownwall[cf], ynw, yupdownwall[!cf], j); } } } } // brace woot! } } } } #endif // Check for ".nextsector is its own sector" if (ns == i) { if (!bad) { const int32_t onumct = numcorruptthings; const 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<=printfromlev) { if (safetoclear) OSD_Printf(" will clear wall %d's nextwall and nextsector on tryfix\n", j); else suggest_nextsector_correction(nw, j); } } } } // Check for ".nextwall already referenced from wall ..." if (!corruptcheck_noalreadyrefd && nw>=0 && nw>3]&(1<<(nw&7))) { const int32_t onumct = numcorruptthings; const int16_t lnws = lastnextwallsource[nw]; const int16_t nwnw = wall[nw].nextwall; CORRUPTCHK_PRINT(3, CORRUPT_WALL|j, "WALL[%d].NEXTWALL=%d already referenced from wall %d", j, nw, lnws); if (onumct < MAXCORRUPTTHINGS && (nwnw==j || nwnw==lnws)) { const int32_t walltoclear = nwnw==j ? lnws : j; if (tryfixing & (1ull<= 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; } } // Various checks of .nextsector and .nextwall if (bad < 4) { const 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<=printfromlev) suggest_nextsector_correction(nw, j); } } else if (ns>=0) { if (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<= 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); } } #if CCHK_LOOP_CHECKS // Wall loop checks. if (bad < 4 && numw >= 4) { const int32_t outerloopstart = determine_outer_loop(i); if (outerloopstart == -1) CORRUPTCHK_PRINT(2, CORRUPT_SECTOR|i, "SECTOR %d contains no outer (clockwise) loop", i); else if (outerloopstart == -2) CORRUPTCHK_PRINT(2, CORRUPT_SECTOR|i, "SECTOR %d contains more than one outer (clockwise) loops", i); # if CCHK_LOOP_CHECKS >= 2 else { // Now, check for whether every wall-point of every inner loop of // this sector () is inside the outer one. for (j=w0; j<=endwall; /* will step by loops */) { const int32_t nextloopstart = get_nextloopstart(j); if (j != outerloopstart) { int32_t k; for (k=j; k=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 (corruptcheck_game_duke3d) { const int32_t tilenum = sprite[i].picnum; if (tilenum >= 1 && tilenum <= 9 && (sprite[i].cstat&48)) { const int32_t onumct = numcorruptthings; CORRUPTCHK_PRINT(1, CORRUPT_SPRITE|i, "%s sprite %d is not face-aligned", names[tilenum], i); if (onumct < MAXCORRUPTTHINGS) { if (tryfixing & (1ull<= printfromlev) OSD_Printf(" suggest clearing sprite[%d].cstat bits 16 and 32\n", i); } } } if (klabs(sprite[i].x) > BXY_MAX || klabs(sprite[i].y) > BXY_MAX) { const 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, ok=0; const int32_t sect = sprite[i].sectnum; if ((unsigned)sect < (unsigned)numsectors) { const int32_t firstwall = sector[sect].wallptr; if ((unsigned)firstwall < (unsigned)numwalls) { x = wall[firstwall].x; y = wall[firstwall].y; ok = 1; } } if (!(tryfixing & (1ull<= 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; idescription[i]); } static int32_t M_HaveDescription(StatusBarMenu *m) { int32_t i; for (i=0; idescription[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; inumentries; i++) if (m->auxdata[i]==auxdata) { for (j=i; jnumentries-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; inumentries; 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; inumentries; 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 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; rdescription[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); videoShowFrame(1); } printext16(xpos, ypos+row*MENU_Y_SPACING, MENU_FG_COLOR, MENU_BG_COLOR, disptext, 0); videoShowFrame(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 || tilesiz[picnum].x <= 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= 0) { int32_t k = 0; for (j=0; j= 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 1) { for (i=0; i>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); #ifdef __cplusplus extern "C" { #endif extern void LM_Register(const char *name, luamenufunc_t funcptr, const char *description); extern void LM_Clear(void); #ifdef __cplusplus } #endif 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