/ source / blood / src / m32common.cpp
m32common.cpp
   1  // This object is shared by the editors of *all* Build games!
   2  
   3  #include "compat.h"
   4  #include "keys.h"
   5  #include "build.h"
   6  #include "cache1d.h"
   7  #ifdef POLYMER
   8  # include "polymer.h"
   9  #endif
  10  #include "editor.h"
  11  #include "renderlayer.h"
  12  
  13  #include "m32script.h"
  14  #include "m32def.h"
  15  
  16  #include "lz4.h"
  17  #include "xxhash.h"
  18  
  19  // 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.
  20  #include "common_game.h"
  21  
  22  #include "vfs.h"
  23  
  24  //////////////////// Key stuff ////////////////////
  25  
  26  #define eitherALT   (keystatus[KEYSC_LALT] || keystatus[KEYSC_RALT])
  27  #define eitherCTRL  (keystatus[KEYSC_LCTRL] || keystatus[KEYSC_RCTRL])
  28  #define eitherSHIFT (keystatus[KEYSC_LSHIFT] || keystatus[KEYSC_RSHIFT])
  29  
  30  #define PRESSED_KEYSC(Key) (keystatus[KEYSC_##Key] && !(keystatus[KEYSC_##Key]=0))
  31  
  32  ////
  33  
  34  // All these variables need verification that all relevant editor stubs are actually implementing them correctly.
  35  
  36  char getmessage[162], getmessageleng;
  37  int32_t getmessagetimeoff; //, charsperline;
  38  
  39  int32_t mousxplc, mousyplc;
  40  int32_t mouseaction;
  41  
  42  char *scripthist[SCRIPTHISTSIZ];
  43  int32_t scripthistend;
  44  
  45  int32_t g_lazy_tileselector;
  46  int32_t fixmaponsave_sprites = 1;
  47  
  48  int32_t showambiencesounds=2;
  49  
  50  int32_t autosave=180;
  51  
  52  int32_t autocorruptcheck;
  53  int32_t corruptcheck_noalreadyrefd, corruptcheck_heinum=1;
  54  int32_t corruptcheck_game_duke3d=1;  // TODO: at startup, make conditional on which game we are editing for?
  55  int32_t corrupt_tryfix_alt;
  56  int32_t corruptlevel, numcorruptthings, corruptthings[MAXCORRUPTTHINGS];
  57  
  58  ////
  59  
  60  #ifdef YAX_ENABLE
  61  const char *yupdownwall[2] = {"upwall","downwall"};
  62  const char *YUPDOWNWALL[2] = {"UPWALL","DOWNWALL"};
  63  #endif
  64  
  65  ////
  66  
  67  void drawgradient(void)
  68  {
  69      int32_t i, col = editorcolors[25];
  70      videoBeginDrawing();
  71      for (i=ydim-STATUS2DSIZ+16; i<ydim && col>0; i++,col--)
  72          CLEARLINES2D(i, 1, (col<<24)|(col<<16)|(col<<8)|col);
  73      CLEARLINES2D(i, ydim-i, 0);
  74      videoEndDrawing();
  75  }
  76  
  77  static void message_common1(const char *tmpstr)
  78  {
  79      Bstrncpyz(getmessage, tmpstr, sizeof(getmessage));
  80  
  81      getmessageleng = Bstrlen(getmessage);
  82      getmessagetimeoff = (int32_t) totalclock + 120*2 + getmessageleng*(120/30);
  83  //    lastmessagetime = totalclock;
  84  }
  85  
  86  void message(const char *fmt, ...)
  87  {
  88      char tmpstr[256];
  89      va_list va;
  90  
  91      va_start(va, fmt);
  92      Bvsnprintf(tmpstr, 256, fmt, va);
  93      va_end(va);
  94  
  95      message_common1(tmpstr);
  96  
  97      if (!mouseaction)
  98          OSD_Printf("%s\n", tmpstr);
  99  }
 100  
 101  void silentmessage(const char *fmt, ...)
 102  {
 103      char tmpstr[256];
 104      va_list va;
 105  
 106      va_start(va, fmt);
 107      Bvsnprintf(tmpstr, 256, fmt, va);
 108      va_end(va);
 109  
 110      message_common1(tmpstr);
 111  }
 112  
 113  ////////// tag labeling system //////////
 114  
 115  typedef struct
 116  {
 117      hashtable_t hashtab;
 118      char *label[32768];
 119      int32_t numlabels;
 120  } taglab_t;
 121  
 122  static taglab_t g_taglab;
 123  
 124  static void tstrtoupper(char *s)
 125  {
 126      int32_t i;
 127      for (i=0; s[i]; i++)
 128          s[i] = Btoupper(s[i]);
 129  }
 130  
 131  void taglab_init()
 132  {
 133      int32_t i;
 134  
 135      g_taglab.numlabels = 0;
 136      g_taglab.hashtab.size = 16384;
 137      hash_init(&g_taglab.hashtab);
 138  
 139      for (i=0; i<32768; i++)
 140          DO_FREE_AND_NULL(g_taglab.label[i]);
 141  }
 142  
 143  int32_t taglab_load(const char *filename, int32_t flags)
 144  {
 145      int32_t len, i;
 146      char buf[BMAX_PATH], *dot, *filebuf;
 147  
 148      taglab_init();
 149  
 150      len = Bstrlen(filename);
 151      if (len >= BMAX_PATH-1)
 152          return -1;
 153      Bmemcpy(buf, filename, len+1);
 154  
 155      //
 156      dot = Bstrrchr(buf, '.');
 157      if (!dot)
 158          dot = &buf[len];
 159  
 160      if (dot-buf+8 >= BMAX_PATH)
 161          return -1;
 162      Bmemcpy(dot, ".maptags", 9);
 163      //
 164  
 165      buildvfs_kfd fil;
 166      if ((fil = kopen4load(buf,flags)) == buildvfs_kfd_invalid)
 167          return -1;
 168  
 169      len = kfilelength(fil);
 170  
 171      filebuf = (char *)Xmalloc(len+1);
 172      if (!filebuf)
 173      {
 174          kclose(fil);
 175          return -1;
 176      }
 177  
 178      kread(fil, filebuf, len);
 179      filebuf[len] = 0;
 180      kclose(fil);
 181  
 182      // ----
 183  
 184      {
 185          int32_t tag;
 186          char *cp=filebuf, *bp, *ep;
 187  
 188          while (1)
 189          {
 190  #define XTAGLAB_STRINGIFY(X) TAGLAB_STRINGIFY(X)
 191  #define TAGLAB_STRINGIFY(X) #X
 192              i = sscanf(cp, "%d %" XTAGLAB_STRINGIFY(TAGLAB_MAX) "s", &tag, buf);
 193  #undef XTAGLAB_STRINGIFY
 194  #undef TAGLAB_STRINGIFY
 195              if (i != 2 || !buf[0] || tag<0 || tag>=32768)
 196                  goto nextline;
 197  
 198              buf[TAGLAB_MAX-1] = 0;
 199  
 200              i = Bstrlen(buf);
 201              bp = buf; while (*bp && isspace(*bp)) bp++;
 202              ep = &buf[i-1]; while (ep>buf && isspace(*ep)) ep--;
 203              ep++;
 204  
 205              if (!(ep > bp))
 206                  goto nextline;
 207              *ep = 0;
 208  
 209              taglab_add(bp, tag);
 210  //initprintf("add tag %s:%d\n", bp, tag);
 211  nextline:
 212              while (*cp && *cp!='\n')
 213                  cp++;
 214              while (*cp=='\r' || *cp=='\n')
 215                  cp++;
 216              if (*cp == 0)
 217                  break;
 218          }
 219      }
 220  
 221      // ----
 222      Xfree(filebuf);
 223  
 224      return 0;
 225  }
 226  
 227  int32_t taglab_save(const char *mapname)
 228  {
 229      int32_t len, i;
 230      char buf[BMAX_PATH], *dot;
 231      const char *label;
 232  
 233      if (g_taglab.numlabels==0)
 234          return 1;
 235  
 236      Bstrncpyz(buf, mapname, BMAX_PATH);
 237  
 238      len = Bstrlen(buf);
 239      //
 240      dot = Bstrrchr(buf, '.');
 241      if (!dot)
 242          dot = &buf[len];
 243  
 244      if (dot-buf+8 >= BMAX_PATH)
 245          return -1;
 246      Bmemcpy(dot, ".maptags", 9);
 247      //
 248  
 249      buildvfs_fd fil;
 250      if ((fil = buildvfs_open_write(buf)) == buildvfs_fd_invalid)
 251      {
 252          initprintf("Couldn't open \"%s\" for writing: %s\n", buf, strerror(errno));
 253          return -1;
 254      }
 255  
 256      for (i=0; i<32768; i++)
 257      {
 258          label = taglab_getlabel(i);
 259          if (!label)
 260              continue;
 261  
 262          len = Bsprintf(buf, "%d %s" OURNEWL, i, label);
 263          if (buildvfs_write(fil, buf, len)!=len)
 264              break;
 265      }
 266  
 267      buildvfs_close(fil);
 268  
 269      return (i!=32768);
 270  }
 271  
 272  int32_t taglab_add(const char *label, int16_t tag)
 273  {
 274      const char *otaglabel;
 275      char buf[TAGLAB_MAX];
 276      int32_t olabeltag, diddel=0;
 277  
 278      if (tag < 0)
 279          return -1;
 280  
 281      Bstrncpyz(buf, label, sizeof(buf));
 282      // upcase the tag for storage and comparison
 283      tstrtoupper(buf);
 284  
 285      otaglabel = g_taglab.label[tag];
 286      if (otaglabel)
 287      {
 288          if (!Bstrcasecmp(otaglabel, buf))
 289              return 0;
 290  
 291  //        hash_delete(&g_taglab.hashtab, g_taglab.label[tag]);
 292  
 293          // a label having the same tag number as 'tag' is deleted
 294          DO_FREE_AND_NULL(g_taglab.label[tag]);
 295          diddel |= 1;
 296      }
 297      else
 298      {
 299          olabeltag = hash_findcase(&g_taglab.hashtab, buf);
 300          if (olabeltag==tag)
 301              return 0;
 302  
 303          if (olabeltag>=0)
 304          {
 305              // the label gets assigned to a new tag number ('tag deleted')
 306              DO_FREE_AND_NULL(g_taglab.label[olabeltag]);
 307              diddel |= 2;
 308          }
 309      }
 310  
 311      if (!diddel)
 312          g_taglab.numlabels++;
 313      g_taglab.label[tag] = Xstrdup(buf);
 314  //initprintf("added %s %d to hash\n", g_taglab.label[tag], tag);
 315      hash_add(&g_taglab.hashtab, g_taglab.label[tag], tag, 1);
 316  
 317      return diddel;
 318  }
 319  
 320  const char *taglab_getlabel(int16_t tag)
 321  {
 322      if (tag < 0)  // || tag>=32768 implicitly
 323          return NULL;
 324  
 325      return g_taglab.label[tag];
 326  }
 327  
 328  int32_t taglab_gettag(const char *label)
 329  {
 330      char buf[TAGLAB_MAX];
 331  
 332      Bstrncpyz(buf, label, TAGLAB_MAX);
 333  
 334      // need to upcase since hash_findcase doesn't work as expected:
 335      // getting the code is still (necessarily) case-sensitive...
 336      tstrtoupper(buf);
 337  
 338      return hash_findcase(&g_taglab.hashtab, buf);
 339  }
 340  ////////// end tag labeling system //////////
 341  
 342  ////////// UNDO/REDO SYSTEM //////////
 343  #if M32_UNDO
 344  mapundo_t *mapstate = NULL;
 345  
 346  int32_t map_revision = 1;
 347  
 348  static int32_t try_match_with_prev(int32_t idx, int32_t numsthgs, uintptr_t crc)
 349  {
 350      if (mapstate->prev && mapstate->prev->num[idx]==numsthgs && mapstate->prev->crc[idx]==crc)
 351      {
 352          // found match!
 353          mapstate->lz4Blocks[idx] = mapstate->prev->lz4Blocks[idx];
 354          mapstate->lz4Size[idx] = mapstate->prev->lz4Size[idx];
 355          (*(int32_t *)mapstate->lz4Blocks[idx])++;  // increase refcount!
 356  
 357          return 1;
 358      }
 359  
 360      return 0;
 361  }
 362  
 363  static void create_compressed_block(int32_t idx, const void *srcdata, uint32_t size, uintptr_t crc)
 364  {
 365      // allocate
 366      int const compressed_size = LZ4_compressBound(size);
 367      Bassert(compressed_size);
 368      mapstate->lz4Blocks[idx] = (char *)Xmalloc(4+compressed_size);
 369  
 370      // compress & realloc
 371      mapstate->lz4Size[idx] = LZ4_compress_default((const char*)srcdata, mapstate->lz4Blocks[idx]+4, size, compressed_size);
 372      Bassert(mapstate->lz4Size[idx] > 0);
 373      mapstate->lz4Blocks[idx] = (char *)Xrealloc(mapstate->lz4Blocks[idx], 4+mapstate->lz4Size[idx]);
 374  
 375      // write refcount
 376      *(int32_t *)mapstate->lz4Blocks[idx] = 1;
 377  
 378      mapstate->crc[idx] = crc;
 379  }
 380  
 381  static void free_self_and_successors(mapundo_t *mapst)
 382  {
 383      mapundo_t *cur = mapst;
 384  
 385      mapst->prev = NULL;  // break the back link
 386  
 387      while (cur->next)
 388          cur = cur->next;
 389  
 390      while (1)
 391      {
 392          int32_t i;
 393          mapundo_t *const prev = cur->prev;
 394  
 395          for (i=0; i<3; i++)
 396          {
 397              int32_t *const refcnt = (int32_t *)cur->lz4Blocks[i];
 398  
 399              if (refcnt)
 400              {
 401                  (*refcnt)--;
 402                  if (*refcnt == 0)
 403                      Xfree(refcnt);  // free the block!
 404              }
 405          }
 406  
 407          Xfree(cur);
 408  
 409          if (!prev)
 410              break;
 411  
 412          cur = prev;
 413      }
 414  }
 415  
 416  // NOTE: only _consecutive_ matching (size+crc) sector/wall/sprite blocks are
 417  // shared!
 418  void create_map_snapshot(void)
 419  {
 420      if (mapstate == NULL)
 421      {
 422          // create initial mapstate
 423  
 424          map_revision = 1;
 425  
 426          mapstate = (mapundo_t *)Xcalloc(1, sizeof(mapundo_t));
 427          mapstate->revision = map_revision;
 428          mapstate->prev = mapstate->next = NULL;
 429      }
 430      else
 431      {
 432          if (mapstate->next)
 433              free_self_and_successors(mapstate->next);
 434          // now, have no successors
 435  
 436          // calloc because not everything may be set in the following:
 437          mapstate->next = (mapundo_t *)Xcalloc(1, sizeof(mapundo_t));
 438          mapstate->next->prev = mapstate;
 439  
 440          mapstate = mapstate->next;
 441  
 442          mapstate->revision = ++map_revision;
 443      }
 444  
 445  
 446      fixspritesectors();
 447  
 448      mapstate->num[UNDO_SECTORS] = numsectors;
 449      mapstate->num[UNDO_WALLS]   = numwalls;
 450      mapstate->num[UNDO_SPRITES] = Numsprites;
 451  
 452      if (numsectors)
 453      {
 454  #if !defined UINTPTR_MAX
 455  # error Need UINTPTR_MAX define to select between 32- and 64-bit functions
 456  #endif
 457  #if UINTPTR_MAX == 0xffffffff
 458          /* 32-bit */
 459  #define XXH__ XXH32
 460  #else
 461          /* 64-bit */
 462  #define XXH__ XXH64
 463  #endif
 464          uintptr_t temphash = XXH__((uint8_t *)sector, numsectors*sizeof(sectortype), numsectors*sizeof(sectortype));
 465  
 466          if (!try_match_with_prev(0, numsectors, temphash))
 467              create_compressed_block(0, sector, numsectors*sizeof(sectortype), temphash);
 468  
 469          if (numwalls)
 470          {
 471              temphash = XXH__((uint8_t *)wall, numwalls*sizeof(walltype), numwalls*sizeof(walltype));
 472  
 473              if (!try_match_with_prev(1, numwalls, temphash))
 474                  create_compressed_block(1, wall, numwalls*sizeof(walltype), temphash);
 475          }
 476  
 477          if (Numsprites)
 478          {
 479              temphash = XXH__((uint8_t *)sprite, MAXSPRITES*sizeof(spritetype), MAXSPRITES*sizeof(spritetype));
 480  
 481              if (!try_match_with_prev(2, Numsprites, temphash))
 482              {
 483                  int32_t i = 0;
 484                  auto const uspri = (uspritetype *)Xmalloc(Numsprites*sizeof(spritetype) + 4);
 485                  auto spri = uspri;
 486  
 487                  for (bssize_t j=0; j<MAXSPRITES && i < Numsprites; j++)
 488                      if (sprite[j].statnum != MAXSTATUS)
 489                      {
 490                          Bmemcpy(spri++, &sprite[j], sizeof(spritetype));
 491                          i++;
 492                      }
 493  
 494                  create_compressed_block(2, uspri, Numsprites*sizeof(spritetype), temphash);
 495                  Xfree(uspri);
 496              }
 497          }
 498  #undef XXH__
 499      }
 500  
 501      CheckMapCorruption(5, 0);
 502  }
 503  
 504  void map_undoredo_free(void)
 505  {
 506      if (mapstate)
 507      {
 508          free_self_and_successors(mapstate);
 509          mapstate = NULL;
 510      }
 511  
 512      map_revision = 1;
 513  }
 514  
 515  int32_t map_undoredo(int dir)
 516  {
 517      if (mapstate == NULL) return 1;
 518  
 519      auto const which = dir ? mapstate->next : mapstate->prev;
 520      if (which == NULL || !which->num[UNDO_SECTORS]) return 1;
 521  
 522      mapstate = which;
 523  
 524      numsectors   = mapstate->num[UNDO_SECTORS];
 525      numwalls     = mapstate->num[UNDO_WALLS];
 526      map_revision = mapstate->revision;
 527  
 528      Bmemset(show2dsector, 0, sizeof(show2dsector));
 529  
 530      reset_highlightsector();
 531      reset_highlight();
 532  
 533      initspritelists();
 534  
 535      if (mapstate->num[UNDO_SECTORS])
 536      {
 537          // restore sector[]
 538          auto bytes = LZ4_decompress_safe(mapstate->lz4Blocks[UNDO_SECTORS]+4, (char*)sector, mapstate->lz4Size[UNDO_SECTORS], MAXSECTORS*sizeof(sectortype));
 539          Bassert(bytes > 0);
 540  
 541          if (mapstate->num[UNDO_WALLS])  // restore wall[]
 542          {
 543              bytes = LZ4_decompress_safe(mapstate->lz4Blocks[UNDO_WALLS]+4, (char*)wall, mapstate->lz4Size[UNDO_WALLS], MAXWALLS*sizeof(walltype));
 544              Bassert(bytes > 0);
 545          }
 546  
 547          if (mapstate->num[UNDO_SPRITES])  // restore sprite[]
 548          {
 549              bytes = LZ4_decompress_safe(mapstate->lz4Blocks[UNDO_SPRITES]+4, (char*)sprite, mapstate->lz4Size[UNDO_SPRITES], MAXSPRITES*sizeof(spritetype));
 550              Bassert(bytes > 0);
 551          }
 552      }
 553  
 554      // insert sprites
 555      for (int i=0; i<mapstate->num[UNDO_SPRITES]; i++)
 556      {
 557          if ((sprite[i].cstat & 48) == 48) sprite[i].cstat &= ~48;
 558          Bassert((unsigned)sprite[i].sectnum < (unsigned)numsectors
 559                     && (unsigned)sprite[i].statnum < MAXSTATUS);
 560          insertsprite(sprite[i].sectnum, sprite[i].statnum);
 561      }
 562  
 563      Bassert(Numsprites == mapstate->num[UNDO_SPRITES]);
 564  
 565  #ifdef POLYMER
 566      if (in3dmode() && videoGetRenderMode() == REND_POLYMER)
 567          polymer_loadboard();
 568  #endif
 569  #ifdef YAX_ENABLE
 570      yax_update(0);
 571      yax_updategrays(pos.z);
 572  #endif
 573      CheckMapCorruption(4, 0);
 574  
 575      return 0;
 576  }
 577  #endif
 578  
 579  ////
 580  
 581  //// port of a.m32's corruptchk ////
 582  // Compile wall loop checks? 0: no, 1: partial, 2: full.
 583  #define CCHK_LOOP_CHECKS 0
 584  // returns value from 0 (all OK) to 5 (panic!)
 585  #define CCHK_PANIC OSDTEXT_DARKRED "PANIC!!!^O "
 586  //#define CCHKPREF OSDTEXT_RED "^O"
 587  #define CCHK_CORRECTED OSDTEXT_GREEN " -> "
 588  
 589  #define CORRUPTCHK_PRINT(errlev, what, fmt, ...) do  \
 590  { \
 591      bad = max(bad, errlev); \
 592      if (numcorruptthings>=MAXCORRUPTTHINGS) \
 593          goto too_many_errors; \
 594      corruptthings[numcorruptthings++] = (what); \
 595      if (errlev >= printfromlev) \
 596          OSD_Printf("#%d: " fmt "\n", numcorruptthings, ## __VA_ARGS__); \
 597  } while (0)
 598  
 599  #ifdef YAX_ENABLE
 600  static int32_t walls_have_equal_endpoints(int32_t w1, int32_t w2)
 601  {
 602      int32_t n1 = wall[w1].point2, n2 = wall[w2].point2;
 603  
 604      return (wall[w1].x==wall[w2].x && wall[w1].y==wall[w2].y &&
 605              wall[n1].x==wall[n2].x && wall[n1].y==wall[n2].y);
 606  }
 607  
 608  static void correct_yax_nextwall(int32_t wallnum, int32_t bunchnum, int32_t cf, int32_t tryfixingp)
 609  {
 610      int32_t i, j, startwall, endwall;
 611      int32_t nummatching=0, lastwall[2] = { -1, -1 };
 612  
 613      for (SECTORS_OF_BUNCH(bunchnum, !cf, i))
 614          for (WALLS_OF_SECTOR(i, j))
 615          {
 616              //  v v v shouldn't happen, 'stupidity safety'
 617              if (j!=wallnum && walls_have_equal_endpoints(wallnum, j))
 618              {
 619                  lastwall[nummatching++] = j;
 620                  if (nummatching==2)
 621                      goto outofloop;
 622              }
 623          }
 624  outofloop:
 625      if (nummatching==1)
 626      {
 627          if (!tryfixingp)
 628          {
 629              OSD_Printf("    will set wall %d's %s to %d on tryfix\n",
 630                         wallnum, yupdownwall[cf], lastwall[0]);
 631          }
 632          else
 633          {
 634              int32_t setreverse = 0;
 635              yax_setnextwall(wallnum, cf, lastwall[0]);
 636              if (yax_getnextwall(lastwall[0], !cf) < 0)
 637              {
 638                  setreverse = 1;
 639                  yax_setnextwall(lastwall[0], !cf, wallnum);
 640              }
 641  
 642              OSD_Printf("auto-correction: set wall %d's %s to %d%s\n",
 643                         wallnum, yupdownwall[cf], lastwall[0], setreverse?" and its reverse link":"");
 644          }
 645      }
 646      else if (!tryfixingp)
 647      {
 648          if (nummatching > 1)
 649          {
 650              OSD_Printf("    found more than one matching wall: at least %d and %d\n",
 651                         lastwall[0], lastwall[1]);
 652          }
 653          else if (nummatching == 0)
 654          {
 655              OSD_Printf("    found no matching walls!\n");
 656          }
 657      }
 658  }
 659  #endif
 660  
 661  // in reverse orientation
 662  static int32_t walls_are_consistent(int32_t w1, int32_t w2)
 663  {
 664      return (wall[w2].x==POINT2(w1).x && wall[w2].y==POINT2(w1).y &&
 665              wall[w1].x==POINT2(w2).x && wall[w1].y==POINT2(w2).y);
 666  }
 667  
 668  static void suggest_nextsector_correction(int32_t nw, int32_t j)
 669  {
 670      // wall j's nextsector is inconsistent with its nextwall... what shall we do?
 671  
 672      if (nw>=0 && nw<numwalls)
 673      {
 674          // maybe wall[j].nextwall's nextwall is right?
 675          if (wall[nw].nextwall==j && walls_are_consistent(nw, j))
 676              OSD_Printf("   suggest setting wall[%d].nextsector to %d\n",
 677                         j, sectorofwall_noquick(nw));
 678      }
 679  
 680      // alternative
 681      if (wall[j].nextsector>=0 && wall[j].nextsector<numsectors)
 682      {
 683          int32_t w, startwall, endwall;
 684          for (WALLS_OF_SECTOR(wall[j].nextsector, w))
 685          {
 686              // XXX: need clearing some others?
 687              if (walls_are_consistent(w, j))
 688              {
 689                  OSD_Printf(" ? suggest setting wall[%d].nextwall to %d\n",
 690                             j, w);
 691                  break;
 692              }
 693          }
 694      }
 695  
 696      OSD_Printf(" ?? suggest making wall %d white\n", j);
 697  }
 698  
 699  static void do_nextsector_correction(int32_t nw, int32_t j)
 700  {
 701      if (corrupt_tryfix_alt==0)
 702      {
 703          if (nw>=0 && nw<numwalls)
 704              if (wall[nw].nextwall==j && walls_are_consistent(nw, j))
 705              {
 706                  int32_t newns = sectorofwall_noquick(nw);
 707                  wall[j].nextsector = newns;
 708                  OSD_Printf(CCHK_CORRECTED "auto-correction: set wall[%d].nextsector=%d\n",
 709                             j, newns);
 710              }
 711      }
 712      else if (corrupt_tryfix_alt==1)
 713      {
 714          if (wall[j].nextsector>=0 && wall[j].nextsector<numsectors)
 715          {
 716              int32_t w, startwall, endwall;
 717              for (WALLS_OF_SECTOR(wall[j].nextsector, w))
 718                  if (walls_are_consistent(w, j))
 719                  {
 720                      wall[j].nextwall = w;
 721                      OSD_Printf(CCHK_CORRECTED "auto-correction: set wall[%d].nextwall=%d\n",
 722                                 j, w);
 723                      break;
 724                  }
 725          }
 726      }
 727      else if (corrupt_tryfix_alt==2)
 728      {
 729          wall[j].nextwall = wall[j].nextsector = -1;
 730          OSD_Printf(CCHK_CORRECTED "auto-correction: made wall %d white\n", j);
 731      }
 732  }
 733  
 734  
 735  static int32_t csc_s, csc_i;
 736  // 1: corrupt, 0: OK
 737  static int32_t check_spritelist_consistency()
 738  {
 739      int32_t ournumsprites=0;
 740      static uint8_t havesprite[bitmap_size(MAXSPRITES)];
 741  
 742      csc_s = csc_i = -1;
 743  
 744      if (Numsprites < 0 || Numsprites > MAXSPRITES)
 745          return 1;
 746  
 747      for (bssize_t i=0; i<MAXSPRITES; i++)
 748      {
 749          const int32_t sectnum=sprite[i].sectnum, statnum=sprite[i].statnum;
 750  
 751          csc_i = i;
 752  
 753          if ((statnum==MAXSTATUS) != (sectnum==-1))
 754              return 2;  // violation of .statnum==MAXSTATUS iff .sectnum==-1
 755  
 756          if ((unsigned)statnum > MAXSTATUS || (sectnum!=-1 && (unsigned)sectnum > (unsigned)numsectors))
 757              return 3;  // oob sectnum or statnum
 758  
 759          if (statnum != MAXSTATUS)
 760              ournumsprites++;
 761      }
 762  
 763      if (ournumsprites != Numsprites)
 764      {
 765          initprintf("ournumsprites=%d, Numsprites=%d\n", ournumsprites, Numsprites);
 766          return 4;  // counting sprites by statnum!=MAXSTATUS inconsistent with Numsprites
 767      }
 768  
 769      // SECTOR LIST
 770  
 771      Bmemset(havesprite, 0, bitmap_size(Numsprites));
 772  
 773      for (bssize_t s=0; s<numsectors; s++)
 774      {
 775          int i;
 776          csc_s = s;
 777  
 778          for (i=headspritesect[s]; i>=0; i=nextspritesect[i])
 779          {
 780              csc_i = i;
 781  
 782              if (i >= MAXSPRITES)
 783                  return 5;  // oob sprite index in list, or Numsprites inconsistent
 784  
 785              if (havesprite[i>>3]&pow2char[i&7])
 786                  return 6;  // have a cycle in the list
 787  
 788              havesprite[i>>3] |= pow2char[i&7];
 789  
 790              if (sprite[i].sectnum != s)
 791                  return 7;  // .sectnum inconsistent with list
 792          }
 793  
 794          if (i!=-1)
 795              return 8;  // various code checks for -1 to break loop
 796      }
 797  
 798      csc_s = -1;
 799      for (bssize_t i=0; i<MAXSPRITES; i++)
 800      {
 801          csc_i = i;
 802  
 803          if (sprite[i].statnum!=MAXSTATUS && !(havesprite[i>>3]&pow2char[i&7]))
 804              return 9;  // have a sprite in the world not in sector list
 805      }
 806  
 807  
 808      // STATUS LIST -- we now clear havesprite[] bits
 809  
 810      for (bssize_t s=0; s<MAXSTATUS; s++)
 811      {
 812          int i;
 813          csc_s = s;
 814  
 815          for (i=headspritestat[s]; i>=0; i=nextspritestat[i])
 816          {
 817              csc_i = i;
 818  
 819              if (i >= MAXSPRITES)
 820                  return 10;  // oob sprite index in list, or Numsprites inconsistent
 821  
 822              // have a cycle in the list, or status list inconsistent with
 823              // sector list (*)
 824              if (!(havesprite[i>>3]&pow2char[i&7]))
 825                  return 11;
 826  
 827              havesprite[i>>3] &= ~pow2char[i&7];
 828  
 829              if (sprite[i].statnum != s)
 830                  return 12;  // .statnum inconsistent with list
 831          }
 832  
 833          if (i!=-1)
 834              return 13;  // various code checks for -1 to break loop
 835      }
 836  
 837      csc_s = -1;
 838      for (bssize_t i=0; i<Numsprites; i++)
 839      {
 840          csc_i = i;
 841  
 842          // Status list contains only a proper subset of the sprites in the
 843          // sector list.  Reverse case is handled by (*)
 844          if (havesprite[i>>3]&pow2char[i&7])
 845              return 14;
 846      }
 847  
 848      return 0;
 849  }
 850  
 851  #if CCHK_LOOP_CHECKS
 852  // Return the least wall index of the outer loop of sector <sectnum>, or
 853  //  -1 if there is none,
 854  //  -2 if there is more than one.
 855  static int32_t determine_outer_loop(int32_t sectnum)
 856  {
 857      int32_t j, outerloopstart = -1;
 858  
 859      const int32_t startwall = sector[sectnum].wallptr;
 860      const int32_t endwall = startwall + sector[sectnum].wallnum - 1;
 861  
 862      for (j=startwall; j<=endwall; j=get_nextloopstart(j))
 863      {
 864          if (clockdir(j) == CLOCKDIR_CW)
 865          {
 866              if (outerloopstart == -1)
 867                  outerloopstart = j;
 868              else if (outerloopstart >= 0)
 869                  return -2;
 870          }
 871      }
 872  
 873      return outerloopstart;
 874  }
 875  #endif
 876  
 877  #define TRYFIX_NONE() (tryfixing == 0ull)
 878  #define TRYFIX_CNUM(onumct) (onumct < MAXCORRUPTTHINGS && (tryfixing & (1ull<<onumct)))
 879  
 880  int32_t CheckMapCorruption(int32_t printfromlev, uint64_t tryfixing)
 881  {
 882      int32_t i, j;
 883      int32_t ewall=0;  // expected wall index
 884  
 885      int32_t errlevel=0, bad=0;
 886      int32_t heinumcheckstat = 0;  // 1, 2
 887  
 888      uint8_t *seen_nextwalls = NULL;
 889      int16_t *lastnextwallsource = NULL;
 890  
 891      numcorruptthings = 0;
 892  
 893      if (numsectors>MAXSECTORS)
 894          CORRUPTCHK_PRINT(5, 0, CCHK_PANIC "SECTOR LIMIT EXCEEDED (MAXSECTORS=%d)!!!", MAXSECTORS);
 895  
 896      if (numwalls>MAXWALLS)
 897          CORRUPTCHK_PRINT(5, 0, CCHK_PANIC "WALL LIMIT EXCEEDED (MAXWALLS=%d)!!!", MAXWALLS);
 898  
 899      if (numsectors>MAXSECTORS || numwalls>MAXWALLS)
 900      {
 901          corruptlevel = bad;
 902          return bad;
 903      }
 904  
 905      if (numsectors==0 || numwalls==0)
 906      {
 907          if (numsectors>0)
 908              CORRUPTCHK_PRINT(5, 0, CCHK_PANIC " Have sectors but no walls!");
 909          if (numwalls>0)
 910              CORRUPTCHK_PRINT(5, 0, CCHK_PANIC " Have walls but no sectors!");
 911          return bad;
 912      }
 913  
 914      if (!corruptcheck_noalreadyrefd)
 915      {
 916          seen_nextwalls = (uint8_t *)Xcalloc(bitmap_size(numwalls),1);
 917          lastnextwallsource = (int16_t *)Xmalloc(numwalls*sizeof(lastnextwallsource[0]));
 918      }
 919  
 920      for (i=0; i<numsectors; i++)
 921      {
 922          const int32_t w0 = sector[i].wallptr;
 923          const int32_t numw = sector[i].wallnum;
 924          const int32_t endwall = w0 + numw - 1;  // inclusive
 925  
 926          bad = 0;
 927  
 928          if (w0 < 0 || w0 > numwalls)
 929          {
 930              if (w0 < 0 || w0 >= MAXWALLS)
 931                  CORRUPTCHK_PRINT(5, CORRUPT_SECTOR|i, "SECTOR[%d].WALLPTR=%d INVALID!!!", i, w0);
 932              else
 933                  CORRUPTCHK_PRINT(5, CORRUPT_SECTOR|i, "SECTOR[%d].WALLPTR=%d out of range (numwalls=%d)", i, w0, numw);
 934          }
 935  
 936          if (w0 != ewall)
 937              CORRUPTCHK_PRINT(4, CORRUPT_SECTOR|i, "SECTOR[%d].WALLPTR=%d inconsistent, expected %d", i, w0, ewall);
 938  
 939          if (numw <= 1)
 940              CORRUPTCHK_PRINT(5, CORRUPT_SECTOR|i, CCHK_PANIC "SECTOR[%d].WALLNUM=%d INVALID!!!", i, numw);
 941          else if (numw==2)
 942              CORRUPTCHK_PRINT(3, CORRUPT_SECTOR|i, "SECTOR[%d].WALLNUM=2, expected at least 3", i);
 943  
 944          ewall += numw;
 945  
 946          if (endwall >= numwalls)
 947              CORRUPTCHK_PRINT(5, CORRUPT_SECTOR|i, "SECTOR[%d]: wallptr+wallnum=%d out of range: numwalls=%d", i, endwall, numwalls);
 948  
 949          // inconsistent cstat&2 and heinum checker
 950          if (corruptcheck_heinum)
 951          {
 952              const char *cflabel[2] = {"ceiling", "floor"};
 953  
 954              for (j=0; j<2; j++)
 955              {
 956                  const int32_t cs = !!(SECTORFLD(i,stat, j)&2);
 957                  const int32_t hn = !!SECTORFLD(i,heinum, j);
 958  
 959                  if (cs != hn && heinumcheckstat <= 1)
 960                  {
 961                      if (numcorruptthings < MAXCORRUPTTHINGS &&
 962                          (heinumcheckstat==1 || (heinumcheckstat==0 && (tryfixing & (1ull<<numcorruptthings)))))
 963                      {
 964                          setslope(i, j, 0);
 965                          OSD_Printf(CCHK_CORRECTED "auto-correction: reset sector %d's %s slope\n",
 966                                     i, cflabel[j]);
 967                          heinumcheckstat = 1;
 968                      }
 969                      else if (corruptcheck_heinum==2 && heinumcheckstat==0)
 970                      {
 971                          CORRUPTCHK_PRINT(1, CORRUPT_SECTOR|i,
 972                                           "SECTOR[%d]: inconsistent %sstat&2 and heinum", i, cflabel[j]);
 973                      }
 974  
 975                      if (heinumcheckstat != 1)
 976                          heinumcheckstat = 2;
 977                  }
 978              }
 979          }
 980  
 981          errlevel = max(errlevel, bad);
 982  
 983          if (bad < 4)
 984          {
 985              for (j=w0; j<=endwall; j++)
 986              {
 987                  const int32_t nw = wall[j].nextwall;
 988                  const int32_t ns = wall[j].nextsector;
 989  
 990                  bad = 0;
 991  
 992                  // First, some basic wall sanity checks.
 993  
 994                  if (wall[j].point2 < w0 || wall[j].point2 > endwall)
 995                  {
 996                      if (wall[j].point2 < 0 || wall[j].point2 >= MAXWALLS)
 997                          CORRUPTCHK_PRINT(5, CORRUPT_WALL|j, CCHK_PANIC "WALL[%d].POINT2=%d INVALID!!!",
 998                                           j, TrackerCast(wall[j].point2));
 999                      else
1000                          CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].POINT2=%d out of range [%d, %d]",
1001                                           j, TrackerCast(wall[j].point2), w0, endwall);
1002                  }
1003  
1004                  if (nw >= numwalls)
1005                  {
1006                      const int32_t onumct = numcorruptthings;
1007  
1008                      if (TRYFIX_NONE())
1009                      {
1010                          if (nw >= MAXWALLS)
1011                              CORRUPTCHK_PRINT(5, CORRUPT_WALL|j, "WALL[%d].NEXTWALL=%d INVALID!!!",
1012                                               j, nw);
1013                          else
1014                              CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTWALL=%d out of range: numwalls=%d",
1015                                               j, nw, numwalls);
1016                          OSD_Printf("    will make wall %d white on tryfix\n", j);
1017                      }
1018                      else if (TRYFIX_CNUM(onumct))  // CODEDUP MAKE_WALL_WHITE
1019                      {
1020                          wall[j].nextwall = wall[j].nextsector = -1;
1021                          OSD_Printf(CCHK_CORRECTED "auto-correction: made wall %d white\n", j);
1022                      }
1023                  }
1024  
1025                  if (ns >= numsectors)
1026                  {
1027                      const int32_t onumct = numcorruptthings;
1028  
1029                      if (TRYFIX_NONE())
1030                      {
1031                          if (ns >= MAXSECTORS)
1032                              CORRUPTCHK_PRINT(5, CORRUPT_WALL|j, "WALL[%d].NEXTSECTOR=%d INVALID!!!",
1033                                               j, ns);
1034                          else
1035                              CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTSECTOR=%d out of range: numsectors=%d",
1036                                               j, ns, numsectors);
1037                          OSD_Printf("    will make wall %d white on tryfix\n", j);
1038                      }
1039                      else if (TRYFIX_CNUM(onumct))  // CODEDUP MAKE_WALL_WHITE
1040                      {
1041                          wall[j].nextwall = wall[j].nextsector = -1;
1042                          OSD_Printf(CCHK_CORRECTED "auto-correction: made wall %d white\n", j);
1043                      }
1044                  }
1045  
1046                  if (nw>=w0 && nw<=endwall)
1047                      CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTWALL is its own sector's wall", j);
1048  
1049                  if (wall[j].x==POINT2(j).x && wall[j].y==POINT2(j).y)
1050                      CORRUPTCHK_PRINT(3, CORRUPT_WALL|j, "WALL[%d] has length 0", j);
1051  
1052  #ifdef YAX_ENABLE
1053                  // Various TROR checks.
1054                  {
1055                      int32_t cf;
1056  
1057                      for (cf=0; cf<2; cf++)
1058                      {
1059                          const int32_t ynw = yax_getnextwall(j, cf);
1060  
1061                          if (ynw >= 0)
1062                          {
1063                              if (ynw >= numwalls)
1064                                  CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d's %s=%d out of range: numwalls=%d",
1065                                                   j, YUPDOWNWALL[cf], ynw, numwalls);
1066                              else
1067                              {
1068                                  int32_t ynextwallok = 1;
1069  
1070                                  if (j == ynw)
1071                                  {
1072                                      CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d's %s is itself",
1073                                                       j, YUPDOWNWALL[cf]);
1074                                      ynextwallok = 0;
1075                                  }
1076                                  else if (!walls_have_equal_endpoints(j, ynw))
1077                                  {
1078                                      CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d's and its %s=%d's "
1079                                                       "endpoints are inconsistent", j, YUPDOWNWALL[cf], ynw);
1080                                      ynextwallok = 0;
1081                                  }
1082  
1083                                  {
1084                                      const int16_t bunchnum = yax_getbunch(i, cf);
1085                                      const int32_t onumct = numcorruptthings;
1086  
1087                                      if (bunchnum < 0 || bunchnum >= numyaxbunches)
1088                                      {
1089                                          if (tryfixing == 0ull)
1090                                          {
1091                                              CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d has %s=%d, "
1092                                                               "but its %s bunchnum=%d is invalid",
1093                                                               j, YUPDOWNWALL[cf], ynw,
1094                                                               cf==YAX_CEILING? "ceiling":"floor", bunchnum);
1095                                              OSD_Printf("    will clear wall %d's %s to -1 on tryfix\n",
1096                                                         j, yupdownwall[cf]);
1097  
1098                                          }
1099                                          else if (tryfixing & (1ull<<onumct))
1100                                          {
1101                                              yax_setnextwall(j, cf, -1);
1102                                              OSD_Printf(CCHK_CORRECTED "auto-correction: cleared wall %d's %s to -1\n",
1103                                                         j, yupdownwall[cf]);
1104                                          }
1105                                      }
1106                                      else if (!ynextwallok && onumct < MAXCORRUPTTHINGS)
1107                                      {
1108                                          if ((tryfixing & (1ull<<onumct)) || 4>=printfromlev)
1109                                              correct_yax_nextwall(j, bunchnum, cf, tryfixing!=0ull);
1110                                      }
1111                                  }
1112  
1113                                  if (ynextwallok)
1114                                  {
1115                                      const int32_t onumct = numcorruptthings;
1116                                      const int32_t ynwp2 = yax_getnextwall(ynw, !cf);
1117  
1118                                      if (ynwp2 != j)
1119                                      {
1120                                          CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL %d's %s=%d's reverse link wrong"
1121                                                           " (expected %d, have %d)", j, YUPDOWNWALL[cf], ynw, j, ynwp2);
1122                                          if (onumct < MAXCORRUPTTHINGS)
1123                                          {
1124                                              if (tryfixing & (1ull<<onumct))
1125                                              {
1126                                                  yax_setnextwall(ynw, !cf, j);
1127                                                  OSD_Printf(CCHK_CORRECTED "auto-correction: set wall %d's %s=%d's %s to %d\n",
1128                                                             j, yupdownwall[cf], ynw, yupdownwall[!cf], j);
1129                                              }
1130                                              else if (4>=printfromlev)
1131                                              {
1132                                                  OSD_Printf("   will set wall %d's %s=%d's %s to %d on tryfix\n",
1133                                                             j, yupdownwall[cf], ynw, yupdownwall[!cf], j);
1134                                              }
1135                                          }
1136                                      }
1137                                  }   // brace woot!
1138                              }
1139                          }
1140                      }
1141                  }
1142  #endif
1143                  // Check for ".nextsector is its own sector"
1144                  if (ns == i)
1145                  {
1146                      if (!bad)
1147                      {
1148                          const int32_t onumct = numcorruptthings;
1149                          const int32_t safetoclear = (nw==j || (wall[nw].nextwall==-1 && wall[nw].nextsector==-1));
1150  
1151                          CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTSECTOR is its own sector", j);
1152                          if (onumct < MAXCORRUPTTHINGS)
1153                          {
1154                              if (tryfixing & (1ull<<onumct))
1155                              {
1156                                  if (safetoclear)
1157                                  {
1158                                      wall[j].nextwall = wall[j].nextsector = -1;
1159                                      OSD_Printf(CCHK_CORRECTED "auto-correction: cleared wall %d's nextwall"
1160                                                 " and nextsector\n", j);
1161                                  }
1162                                  else
1163                                      do_nextsector_correction(nw, j);
1164                              }
1165                              else if (4>=printfromlev)
1166                              {
1167                                  if (safetoclear)
1168                                      OSD_Printf("   will clear wall %d's nextwall and nextsector on tryfix\n", j);
1169                                  else
1170                                      suggest_nextsector_correction(nw, j);
1171                              }
1172                          }
1173                      }
1174                  }
1175  
1176                  // Check for ".nextwall already referenced from wall ..."
1177                  if (!corruptcheck_noalreadyrefd && nw>=0 && nw<numwalls)
1178                  {
1179                      if (seen_nextwalls[nw>>3]&pow2char[nw&7])
1180                      {
1181                          const int32_t onumct = numcorruptthings;
1182  
1183                          const int16_t lnws = lastnextwallsource[nw];
1184                          const int16_t nwnw = wall[nw].nextwall;
1185  
1186                          CORRUPTCHK_PRINT(3, CORRUPT_WALL|j, "WALL[%d].NEXTWALL=%d already referenced from wall %d",
1187                                           j, nw, lnws);
1188  
1189                          if (onumct < MAXCORRUPTTHINGS && (nwnw==j || nwnw==lnws))
1190                          {
1191                              const int32_t walltoclear = nwnw==j ? lnws : j;
1192  
1193                              if (tryfixing & (1ull<<onumct))
1194                              {
1195                                  wall[walltoclear].nextsector = wall[walltoclear].nextwall = -1;
1196                                  OSD_Printf(CCHK_CORRECTED "auto-correction: cleared wall %d's nextwall and nextsector tags to -1\n",
1197                                             walltoclear);
1198                              }
1199                              else if (3 >= printfromlev)
1200                                  OSD_Printf("    wall[%d].nextwall=%d, suggest clearing wall %d's nextwall and nextsector tags to -1\n",
1201                                             nw, nwnw, walltoclear);
1202                          }
1203                      }
1204                      else
1205                      {
1206                          seen_nextwalls[nw>>3] |= 1<<(nw&7);
1207                          lastnextwallsource[nw] = j;
1208                      }
1209                  }
1210  
1211                  // Various checks of .nextsector and .nextwall
1212                  if (bad < 4)
1213                  {
1214                      const int32_t onumct = numcorruptthings;
1215  
1216                      if ((ns^nw)<0)
1217                      {
1218                          CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTSECTOR=%d and .NEXTWALL=%d inconsistent:"
1219                                           " missing one next pointer", j, ns, nw);
1220                          if (onumct < MAXCORRUPTTHINGS)
1221                          {
1222                              if (tryfixing & (1ull<<onumct))
1223                                  do_nextsector_correction(nw, j);
1224                              else if (4>=printfromlev)
1225                                  suggest_nextsector_correction(nw, j);
1226                          }
1227                      }
1228                      else if (ns>=0)
1229                      {
1230                          if (nw<sector[ns].wallptr || nw>=sector[ns].wallptr+sector[ns].wallnum)
1231                          {
1232                              CORRUPTCHK_PRINT(4, CORRUPT_WALL|j, "WALL[%d].NEXTWALL=%d out of .NEXTSECTOR=%d's bounds [%d .. %d]",
1233                                               j, nw, ns, TrackerCast(sector[ns].wallptr), sector[ns].wallptr+sector[ns].wallnum-1);
1234                              if (onumct < MAXCORRUPTTHINGS)
1235                              {
1236                                  if (tryfixing & (1ull<<onumct))
1237                                      do_nextsector_correction(nw, j);
1238                                  else if (4 >= printfromlev)
1239                                      suggest_nextsector_correction(nw, j);
1240                              }
1241                          }
1242  #if 0
1243                          // this one usually appears together with the "already referenced" corruption
1244                          else if (wall[nw].nextsector != i || wall[nw].nextwall != j)
1245                          {
1246                              CORRUPTCHK_PRINT(4, CORRUPT_WALL|nw, "WALL %d nextwall's backreferences inconsistent. Expected nw=%d, ns=%d; got nw=%d, ns=%d",
1247                                               nw, i, j, wall[nw].nextsector, wall[nw].nextwall);
1248                          }
1249  #endif
1250                      }
1251                  }
1252  
1253                  errlevel = max(errlevel, bad);
1254              }
1255          }
1256  #if CCHK_LOOP_CHECKS
1257          // Wall loop checks.
1258          if (bad < 4 && numw >= 4)
1259          {
1260              const int32_t outerloopstart = determine_outer_loop(i);
1261  
1262              if (outerloopstart == -1)
1263                  CORRUPTCHK_PRINT(2, CORRUPT_SECTOR|i, "SECTOR %d contains no outer (clockwise) loop", i);
1264              else if (outerloopstart == -2)
1265                  CORRUPTCHK_PRINT(2, CORRUPT_SECTOR|i, "SECTOR %d contains more than one outer (clockwise) loops", i);
1266  # if CCHK_LOOP_CHECKS >= 2
1267              else
1268              {
1269                  // Now, check for whether every wall-point of every inner loop of
1270                  // this sector (<i>) is inside the outer one.
1271  
1272                  for (j=w0; j<=endwall; /* will step by loops */)
1273                  {
1274                      const int32_t nextloopstart = get_nextloopstart(j);
1275  
1276                      if (j != outerloopstart)
1277                      {
1278                          int32_t k;
1279  
1280                          for (k=j; k<nextloopstart; k++)
1281                              if (!loopinside(wall[k].x, wall[k].y, outerloopstart))
1282                              {
1283                                  CORRUPTCHK_PRINT(4, CORRUPT_WALL|k, "WALL %d (of sector %d) is outside the outer loop", k, i);
1284                                  goto end_wall_loop_checks;
1285                              }
1286                      }
1287  
1288                      j = nextloopstart;
1289                  }
1290  end_wall_loop_checks:
1291                  ;
1292              }
1293  # endif
1294              errlevel = max(errlevel, bad);
1295          }
1296  #endif
1297      }
1298  
1299      bad = 0;
1300      for (i=0; i<MAXSPRITES; i++)
1301      {
1302          if (sprite[i].statnum==MAXSTATUS)
1303              continue;
1304  
1305          if (sprite[i].sectnum<0 || sprite[i].sectnum>=numsectors)
1306              CORRUPTCHK_PRINT(4, CORRUPT_SPRITE|i, "SPRITE[%d].SECTNUM=%d. Expect problems!", i, TrackerCast(sprite[i].sectnum));
1307  
1308          if (sprite[i].statnum<0 || sprite[i].statnum>MAXSTATUS)
1309              CORRUPTCHK_PRINT(4, CORRUPT_SPRITE|i, "SPRITE[%d].STATNUM=%d. Expect problems!", i, TrackerCast(sprite[i].statnum));
1310  
1311          if (sprite[i].picnum<0 || sprite[i].picnum>=MAXTILES)
1312          {
1313              sprite[i].picnum = 0;
1314              CORRUPTCHK_PRINT(0, CORRUPT_SPRITE|i, "SPRITE[%d].PICNUM=%d out of range, resetting to 0", i, TrackerCast(sprite[i].picnum));
1315          }
1316  
1317          if (corruptcheck_game_duke3d)
1318          {
1319              const int32_t tilenum = sprite[i].picnum;
1320  
1321              if (tilenum >= 1 && tilenum <= 9 && (sprite[i].cstat&48))
1322              {
1323                  const int32_t onumct = numcorruptthings;
1324  
1325                  CORRUPTCHK_PRINT(1, CORRUPT_SPRITE|i, "%s sprite %d is not face-aligned",
1326                                   names[tilenum], i);
1327  
1328                  if (onumct < MAXCORRUPTTHINGS)
1329                  {
1330                      if (tryfixing & (1ull<<onumct))
1331                      {
1332                          sprite[i].cstat &= ~(32+16);
1333                          OSD_Printf(CCHK_CORRECTED "auto-correction: cleared sprite[%d].cstat bits 16 and 32\n", i);
1334                      }
1335                      else if (1 >= printfromlev)
1336                          OSD_Printf("   suggest clearing sprite[%d].cstat bits 16 and 32\n", i);
1337                  }
1338              }
1339          }
1340  
1341          if (klabs(sprite[i].x) > BXY_MAX || klabs(sprite[i].y) > BXY_MAX)
1342          {
1343              const int32_t onumct = numcorruptthings;
1344  
1345              CORRUPTCHK_PRINT(3, CORRUPT_SPRITE|i, "SPRITE %d at [%d, %d] is outside the maximal grid range [%d, %d]",
1346                               i, TrackerCast(sprite[i].x), TrackerCast(sprite[i].y), -BXY_MAX, BXY_MAX);
1347  
1348              if (onumct < MAXCORRUPTTHINGS)
1349              {
1350                  int32_t x=0, y=0, ok=0;
1351                  const int32_t sect = sprite[i].sectnum;
1352  
1353                  if ((unsigned)sect < (unsigned)numsectors)
1354                  {
1355                      const int32_t firstwall = sector[sect].wallptr;
1356  
1357                      if ((unsigned)firstwall < (unsigned)numwalls)
1358                      {
1359                          x = wall[firstwall].x;
1360                          y = wall[firstwall].y;
1361                          ok = 1;
1362                      }
1363                  }
1364  
1365                  if (!(tryfixing & (1ull<<onumct)))
1366                  {
1367                      if (ok && 3 >= printfromlev)
1368                          OSD_Printf("   will reposition to its sector's (%d) first"
1369                                     " point [%d,%d] on tryfix\n", sect, x, y);
1370                  }
1371                  else
1372                  {
1373                      if (ok)
1374                      {
1375                          sprite[i].x = x;
1376                          sprite[i].y = y;
1377                          OSD_Printf(CCHK_CORRECTED "auto-correction: repositioned sprite %d to "
1378                                     "its sector's (%d) first point [%d,%d]\n", i, sect, x, y);
1379                      }
1380                  }
1381              }
1382          }
1383      }
1384  
1385      i = check_spritelist_consistency();
1386      if (i)
1387          CORRUPTCHK_PRINT(5, i<0?0:(CORRUPT_SPRITE|i), CCHK_PANIC "SPRITE LISTS CORRUPTED: error code %d, s=%d, i=%d!",
1388                           i, csc_s, csc_i);
1389  
1390      if (0)
1391      {
1392  too_many_errors:
1393          if (printfromlev<=errlevel)
1394              OSD_Printf("!! too many errors, stopping. !!\n");
1395      }
1396  
1397      errlevel = max(errlevel, bad);
1398  
1399      if (errlevel)
1400      {
1401          if (printfromlev<=errlevel)
1402              OSD_Printf("-- corruption level: %d\n", errlevel);
1403          if (tryfixing)
1404              OSD_Printf("--\n");
1405      }
1406  
1407      if (seen_nextwalls)
1408      {
1409          Xfree(seen_nextwalls);
1410          Xfree(lastnextwallsource);
1411      }
1412  
1413      corruptlevel = errlevel;
1414  
1415      return errlevel;
1416  }
1417  ////
1418  
1419  
1420  ////////// STATUS BAR MENU "class" //////////
1421  
1422  #define MENU_MAX_ENTRIES (8*3)
1423  #define MENU_ENTRY_SIZE 25  // max. length of label (including terminating NUL)
1424  
1425  #define MENU_Y_SPACING 8
1426  #define MENU_BASE_Y (ydim-STATUS2DSIZ+32)
1427  
1428  #define MENU_FG_COLOR editorcolors[11]
1429  #define MENU_BG_COLOR editorcolors[0]
1430  #define MENU_BG_COLOR_SEL editorcolors[1]
1431  
1432  #ifdef LUNATIC
1433  # define MENU_HAVE_DESCRIPTION 1
1434  #else
1435  # define MENU_HAVE_DESCRIPTION 0
1436  #endif
1437  
1438  typedef struct StatusBarMenu_ {
1439      const char *const menuname;
1440      const int32_t custom_start_index;
1441      int32_t numentries;
1442  
1443      void (*process_func)(const struct StatusBarMenu_ *m, int32_t col, int32_t row);
1444  
1445      intptr_t auxdata[MENU_MAX_ENTRIES];
1446      char *description[MENU_MAX_ENTRIES];  // strdup'd description string, NULL if non
1447      char name[MENU_MAX_ENTRIES][MENU_ENTRY_SIZE];
1448  } StatusBarMenu;
1449  
1450  #define MENU_INITIALIZER_EMPTY(MenuName, ProcessFunc) \
1451      { MenuName, 0, 0, ProcessFunc, {}, {}, {} }
1452  #define MENU_INITIALIZER(MenuName, CustomStartIndex, ProcessFunc, ...) \
1453      { MenuName, CustomStartIndex, CustomStartIndex, ProcessFunc, {}, {}, ## __VA_ARGS__ }
1454  
1455  #ifdef LUNATIC
1456  static void M_Clear(StatusBarMenu *m)
1457  {
1458      int32_t i;
1459  
1460      m->numentries = 0;
1461      Bmemset(m->auxdata, 0, sizeof(m->auxdata));
1462      Bmemset(m->name, 0, sizeof(m->name));
1463  
1464      for (i=0; i<MENU_MAX_ENTRIES; i++)
1465          DO_FREE_AND_NULL(m->description[i]);
1466  }
1467  
1468  static int32_t M_HaveDescription(StatusBarMenu *m)
1469  {
1470      int32_t i;
1471  
1472      for (i=0; i<MENU_MAX_ENTRIES; i++)
1473          if (m->description[i] != NULL)
1474              return 1;
1475  
1476      return 0;
1477  }
1478  #endif
1479  
1480  // NOTE: Does not handle description strings! (Only the Lua menu uses them.)
1481  static void M_UnregisterFunction(StatusBarMenu *m, intptr_t auxdata)
1482  {
1483      int32_t i, j;
1484  
1485      for (i=m->custom_start_index; i<m->numentries; i++)
1486          if (m->auxdata[i]==auxdata)
1487          {
1488              for (j=i; j<m->numentries-1; j++)
1489              {
1490                  m->auxdata[j] = m->auxdata[j+1];
1491                  Bmemcpy(m->name[j], m->name[j+1], MENU_ENTRY_SIZE);
1492              }
1493  
1494              m->auxdata[j] = 0;
1495              Bmemset(m->name[j], 0, MENU_ENTRY_SIZE);
1496  
1497              m->numentries--;
1498  
1499              break;
1500          }
1501  }
1502  
1503  static void M_RegisterFunction(StatusBarMenu *m, const char *name, intptr_t auxdata, const char *description)
1504  {
1505      int32_t i;
1506  
1507      for (i=8; i<m->numentries; i++)
1508      {
1509          if (m->auxdata[i]==auxdata)
1510          {
1511              // same auxdata, different name
1512              Bstrncpyz(m->name[i], name, MENU_ENTRY_SIZE);
1513              return;
1514          }
1515          else if (!Bstrncmp(m->name[i], name, MENU_ENTRY_SIZE))
1516          {
1517              // same name, different auxdata
1518              m->auxdata[i] = auxdata;
1519              return;
1520          }
1521      }
1522  
1523      if (m->numentries == MENU_MAX_ENTRIES)
1524          return;  // max reached
1525  
1526      Bstrncpyz(m->name[m->numentries], name, MENU_ENTRY_SIZE);
1527      m->auxdata[m->numentries] = auxdata;
1528  
1529  #if MENU_HAVE_DESCRIPTION
1530      // NOTE: description only handled here (not above).
1531      if (description)
1532          m->description[m->numentries] = Xstrdup(description);
1533  #else
1534      UNREFERENCED_PARAMETER(description);
1535  #endif
1536  
1537      m->numentries++;
1538  }
1539  
1540  static void M_DisplayInitial(const StatusBarMenu *m)
1541  {
1542      int32_t x = 8, y = MENU_BASE_Y+16;
1543      int32_t i;
1544  
1545      for (i=0; i<m->numentries; i++)
1546      {
1547          if (i==8 || i==16)
1548          {
1549              x += 208;
1550              y = MENU_BASE_Y+16;
1551          }
1552  
1553          printext16(x,y, MENU_FG_COLOR, MENU_BG_COLOR, m->name[i], 0);
1554          y += MENU_Y_SPACING;
1555      }
1556  
1557      printext16(m->numentries>8 ? 216 : 8, MENU_BASE_Y, MENU_FG_COLOR, -1, m->menuname, 0);
1558  
1559      clearkeys();
1560  }
1561  
1562  static void M_EnterMainLoop(StatusBarMenu *m)
1563  {
1564      char disptext[80];
1565      const int32_t dispwidth = MENU_ENTRY_SIZE-1;
1566  
1567      int32_t i, col=0, row=0;
1568      int32_t crowmax[3] = {-1, -1, -1};
1569      int32_t xpos = 8, ypos = MENU_BASE_Y+16;
1570  
1571      if (m->numentries == 0)
1572      {
1573          printmessage16("%s menu has no entries", m->menuname);
1574          return;
1575      }
1576  
1577      Bmemset(disptext, 0, sizeof(disptext));
1578  
1579      Bassert((unsigned)m->numentries <= MENU_MAX_ENTRIES);
1580      for (i=0; i<=(m->numentries-1)/8; i++)
1581          crowmax[i] = (m->numentries >= (i+1)*8) ? 7 : (m->numentries-1)&7;
1582  
1583      drawgradient();
1584      M_DisplayInitial(m);
1585  
1586      while (keystatus[KEYSC_ESC] == 0)
1587      {
1588          idle_waitevent();
1589          if (handleevents())
1590              quitevent = 0;
1591  
1592          _printmessage16("Select an option, press <Esc> to exit");
1593  
1594          if (PRESSED_KEYSC(DOWN))
1595          {
1596              if (row < crowmax[col])
1597              {
1598                  printext16(xpos, ypos+row*MENU_Y_SPACING, MENU_FG_COLOR, MENU_BG_COLOR, disptext, 0);
1599                  row++;
1600              }
1601          }
1602          else if (PRESSED_KEYSC(UP))
1603          {
1604              if (row > 0)
1605              {
1606                  printext16(xpos, ypos+row*MENU_Y_SPACING, MENU_FG_COLOR, MENU_BG_COLOR, disptext, 0);
1607                  row--;
1608              }
1609          }
1610          else if (PRESSED_KEYSC(LEFT))
1611          {
1612              if (col > 0)
1613              {
1614                  printext16(xpos, ypos+row*8, MENU_FG_COLOR, 0, disptext, 0);
1615                  col--;
1616                  xpos -= 208;
1617                  disptext[dispwidth] = 0;
1618                  row = min(crowmax[col], row);
1619              }
1620          }
1621          else if (PRESSED_KEYSC(RIGHT))
1622          {
1623              if (col < 2 && crowmax[col+1]>=0)
1624              {
1625                  printext16(xpos, ypos+row*8, MENU_FG_COLOR, 0, disptext, 0);
1626                  col++;
1627                  xpos += 208;
1628                  disptext[dispwidth] = 0;
1629                  row = min(crowmax[col], row);
1630              }
1631          }
1632  
1633          for (i=Bsnprintf(disptext, dispwidth, "%s", m->name[col*8 + row]); i < dispwidth; i++)
1634              disptext[i] = ' ';
1635  
1636          if (PRESSED_KEYSC(ENTER))
1637          {
1638              Bassert(m->process_func != NULL);
1639              m->process_func(m, col, row);
1640              break;
1641          }
1642  
1643  #if MENU_HAVE_DESCRIPTION
1644          if (M_HaveDescription(m))
1645          {
1646              const int32_t maxrows = 20;
1647              int32_t r;
1648  
1649              for (r=0; r<maxrows+1; r++)
1650                  printext16(16-4, 16-4 + r*8, 0, MENU_BG_COLOR,  // 71 blanks:
1651                             "                                                                       ", 0);
1652              if (m->description[col*8 + row] != NULL)
1653                  printext16(16, 16, MENU_FG_COLOR, MENU_BG_COLOR, m->description[col*8 + row], 0);
1654          }
1655  #endif
1656          printext16(xpos, ypos+row*MENU_Y_SPACING, MENU_FG_COLOR, MENU_BG_COLOR_SEL, disptext, 0);
1657          videoShowFrame(1);
1658      }
1659  
1660      printext16(xpos, ypos+row*MENU_Y_SPACING, MENU_FG_COLOR, MENU_BG_COLOR, disptext, 0);
1661      videoShowFrame(1);
1662  
1663      keystatus[KEYSC_ESC] = 0;
1664  }
1665  
1666  ////////// SPECIAL FUNCTIONS MENU //////////
1667  
1668  static void FuncMenu_Process(const StatusBarMenu *m, int32_t col, int32_t row);
1669  
1670  static StatusBarMenu g_specialFuncMenu = MENU_INITIALIZER(
1671      "Special functions", 8, FuncMenu_Process,
1672  
1673      {
1674          "Replace invalid tiles",
1675          "Delete all spr of tile #",
1676          "Set map sky shade",
1677          "Set map sky height",
1678          "Global Z coord shift",
1679          "Resize selection",
1680          "Global shade divide",
1681          "Global visibility divide"
1682      }
1683  );
1684  
1685  // "External functions":
1686  
1687  void FuncMenu(void)
1688  {
1689      M_EnterMainLoop(&g_specialFuncMenu);
1690  }
1691  
1692  void registerMenuFunction(const char *funcname, int32_t stateidx)
1693  {
1694      if (funcname)
1695          M_RegisterFunction(&g_specialFuncMenu, funcname, stateidx, NULL);
1696      else
1697          M_UnregisterFunction(&g_specialFuncMenu, stateidx);
1698  }
1699  
1700  // The processing function...
1701  
1702  static int32_t correct_picnum(int16_t *picnumptr)
1703  {
1704      int32_t picnum = *picnumptr;
1705  
1706      if ((unsigned)picnum >= MAXTILES || tilesiz[picnum].x <= 0)
1707      {
1708          *picnumptr = 0;
1709          return 1;
1710      }
1711  
1712      return 0;
1713  }
1714  
1715  static void FuncMenu_Process(const StatusBarMenu *m, int32_t col, int32_t row)
1716  {
1717      int32_t i, j;
1718  
1719      switch (col)
1720      {
1721  
1722      case 1:
1723      case 2:
1724      {
1725          const int32_t stateidx = (Bassert(m->auxdata[col*8 + row] < g_stateCount),
1726                                    m->auxdata[col*8 + row]);
1727  
1728          const char *statename = statesinfo[stateidx].name;
1729          int32_t snlen = Bstrlen(statename);
1730          char *tmpscript = (char *)Xmalloc(1+5+1+snlen+1);
1731  
1732          tmpscript[0] = ' ';  // don't save in history
1733          Bmemcpy(&tmpscript[1], "state", 5);
1734          tmpscript[1+5] = ' ';
1735          Bmemcpy(&tmpscript[1+5+1], statename, snlen);
1736          tmpscript[1+5+1+snlen] = 0;
1737  
1738          M32RunScript(tmpscript);
1739          Xfree(tmpscript);
1740  
1741          if (vm.flags&VMFLAG_ERROR)
1742              printmessage16("There were errors while executing the menu function");
1743          else if (lastpm16time != totalclock)
1744              printmessage16("Menu function executed successfully");
1745      }
1746      break;
1747  
1748      case 0:
1749      {
1750          switch (row)
1751          {
1752  
1753          case 0:
1754          {
1755              j = 0;
1756  
1757              for (i=0; i<MAXSECTORS; i++)
1758              {
1759                  j += correct_picnum(&sector[i].ceilingpicnum);
1760                  j += correct_picnum(&sector[i].floorpicnum);
1761              }
1762  
1763              for (i=0; i<MAXWALLS; i++)
1764              {
1765                  j += correct_picnum(&wall[i].picnum);
1766                  j += correct_picnum(&wall[i].overpicnum);
1767              }
1768              for (i=0; i<MAXSPRITES; i++)
1769                  j += correct_picnum(&sprite[i].picnum);
1770  
1771              printmessage16("Replaced %d invalid tiles",j);
1772          }
1773          break;
1774  
1775          case 1:
1776          {
1777              char tempbuf[64];
1778              Bsprintf(tempbuf,"Delete all sprites of tile #: ");
1779              i = getnumber16(tempbuf,-1,MAXSPRITES-1,1);
1780              if (i >= 0)
1781              {
1782                  int32_t k = 0;
1783                  for (j=0; j<MAXSPRITES; j++)
1784                      if (sprite[j].picnum == i)
1785                          deletesprite(j), k++;
1786                  printmessage16("%d sprite(s) deleted",k);
1787              }
1788              else printmessage16("Aborted");
1789          }
1790          break;
1791  
1792          case 2:
1793          {
1794              j=getnumber16("Set map sky shade:    ",0,128,1);
1795  
1796              for (i=0; i<numsectors; i++)
1797              {
1798                  if (sector[i].ceilingstat&1)
1799                      sector[i].ceilingshade = j;
1800              }
1801              printmessage16("All parallax skies adjusted");
1802          }
1803          break;
1804  
1805          case 3:
1806          {
1807              j=getnumber16("Set map sky height:    ",0,16777216,1);
1808              if (j != 0)
1809              {
1810                  for (i=0; i<numsectors; i++)
1811                  {
1812                      if (sector[i].ceilingstat&1)
1813                          sector[i].ceilingz = j;
1814                  }
1815                  printmessage16("All parallax skies adjusted");
1816              }
1817              else printmessage16("Aborted");
1818          }
1819          break;
1820  
1821          case 4:
1822          {
1823              j=getnumber16("Z offset:    ",0,16777216,1);
1824              if (j!=0)
1825              {
1826                  for (i=0; i<numsectors; i++)
1827                  {
1828                      sector[i].ceilingz += j;
1829                      sector[i].floorz += j;
1830                  }
1831                  for (i=0; i<MAXSPRITES; i++)
1832                      sprite[i].z += j;
1833                  printmessage16("Map adjusted");
1834              }
1835              else printmessage16("Aborted");
1836          }
1837          break;
1838  
1839          case 5:
1840          {
1841              j=getnumber16("Percentage of original:    ",100,1000,0);
1842  
1843              if (j!=100 && j!=0)
1844              {
1845                  int32_t w, currsector, start_wall, end_wall;
1846                  double size = (j/100.f);
1847  
1848                  for (i = 0; i < highlightsectorcnt; i++)
1849                  {
1850                      currsector = highlightsector[i];
1851                      sector[currsector].ceilingz = (int32_t)(sector[currsector].ceilingz*size);
1852                      sector[currsector].floorz = (int32_t)(sector[currsector].floorz*size);
1853  
1854                      // Do all the walls in the sector
1855                      start_wall = sector[currsector].wallptr;
1856                      end_wall = start_wall + sector[currsector].wallnum;
1857  
1858                      for (w = start_wall; w < end_wall; w++)
1859                      {
1860                          wall[w].x = (int32_t)(wall[w].x*size);
1861                          wall[w].y = (int32_t)(wall[w].y*size);
1862                          wall[w].yrepeat = min((int32_t)(wall[w].yrepeat/size),255);
1863                      }
1864  
1865                      w = headspritesect[highlightsector[i]];
1866                      while (w >= 0)
1867                      {
1868                          sprite[w].x = (int32_t)(sprite[w].x*size);
1869                          sprite[w].y = (int32_t)(sprite[w].y*size);
1870                          sprite[w].z = (int32_t)(sprite[w].z*size);
1871                          sprite[w].xrepeat = min(max((int32_t)(sprite[w].xrepeat*size),1),255);
1872                          sprite[w].yrepeat = min(max((int32_t)(sprite[w].yrepeat*size),1),255);
1873                          w = nextspritesect[w];
1874                      }
1875                  }
1876                  printmessage16("Map scaled");
1877              }
1878              else printmessage16("Aborted");
1879          }
1880          break;
1881  
1882          case 6:
1883          {
1884              j=getnumber16("Shade divisor:    ",1,128,1);
1885              if (j > 1)
1886              {
1887                  for (i=0; i<numsectors; i++)
1888                  {
1889                      sector[i].ceilingshade /= j;
1890                      sector[i].floorshade /= j;
1891                  }
1892                  for (i=0; i<numwalls; i++)
1893                      wall[i].shade /= j;
1894                  for (i=0; i<MAXSPRITES; i++)
1895                      sprite[i].shade /= j;
1896                  printmessage16("Shades adjusted");
1897              }
1898              else printmessage16("Aborted");
1899          }
1900          break;
1901  
1902          case 7:
1903          {
1904              j=getnumber16("Visibility divisor:    ",1,128,0);
1905              if (j > 1)
1906              {
1907                  for (i=0; i<numsectors; i++)
1908                  {
1909                      if (sector[i].visibility < 240)
1910                          sector[i].visibility /= j;
1911                      else sector[i].visibility = 240 + (sector[i].visibility>>4)/j;
1912                  }
1913                  printmessage16("Visibility adjusted");
1914              }
1915              else printmessage16("Aborted");
1916          }
1917          break;
1918  
1919          }  // switch (row)
1920      }
1921      break;  // switch (col) / case 0
1922  
1923      }  // switch (col)
1924  }
1925  
1926  void m32_showmouse()
1927  {
1928      int32_t i, col;
1929  
1930      int32_t mousecol = M32_THROB;
1931  
1932      if (whitecol > editorcolors[0])
1933          col = whitecol - mousecol;
1934      else col = whitecol + mousecol;
1935  
1936  #ifdef USE_OPENGL
1937      if (videoGetRenderMode() >= REND_POLYMOST)
1938      {
1939          renderDisableFog();
1940          polymost_useColorOnly(true);
1941      }
1942  #endif
1943  
1944      int const lores = !!(xdim <= 640);
1945  
1946      for (i = (3 - lores); i <= (7 >> lores); i++)
1947      {
1948          plotpixel(searchx+i,searchy,col);
1949          plotpixel(searchx-i,searchy,col);
1950          plotpixel(searchx,searchy-i,col);
1951          plotpixel(searchx,searchy+i,col);
1952      }
1953  
1954      for (i=1; i<=(2 >> lores); i++)
1955      {
1956          plotpixel(searchx+i,searchy,whitecol);
1957          plotpixel(searchx-i,searchy,whitecol);
1958          plotpixel(searchx,searchy-i,whitecol);
1959          plotpixel(searchx,searchy+i,whitecol);
1960      }
1961  
1962      i = (8 >> lores);
1963  
1964      plotpixel(searchx+i,searchy,editorcolors[0]);
1965      plotpixel(searchx-i,searchy,editorcolors[0]);
1966      plotpixel(searchx,searchy-i,editorcolors[0]);
1967      plotpixel(searchx,searchy+i,editorcolors[0]);
1968  
1969      if (!lores)
1970      {
1971          for (i=1; i<=4; i++)
1972          {
1973              plotpixel(searchx+i,searchy,whitecol);
1974              plotpixel(searchx-i,searchy,whitecol);
1975              plotpixel(searchx,searchy-i,whitecol);
1976              plotpixel(searchx,searchy+i,whitecol);
1977          }
1978      }
1979  
1980  #ifdef USE_OPENGL
1981      if (videoGetRenderMode() >= REND_POLYMOST)
1982      {
1983          renderEnableFog();
1984          polymost_useColorOnly(false);
1985      }
1986  #endif
1987  }
1988  
1989  #ifdef LUNATIC
1990  typedef const char *(*luamenufunc_t)(void);
1991  
1992  #ifdef __cplusplus
1993  extern "C" {
1994  #endif
1995  extern void LM_Register(const char *name, luamenufunc_t funcptr, const char *description);
1996  extern void LM_Clear(void);
1997  #ifdef __cplusplus
1998  }
1999  #endif
2000  
2001  static int32_t g_numLuaFuncs = 0;
2002  static luamenufunc_t g_LuaFuncPtrs[MENU_MAX_ENTRIES];
2003  
2004  static void LuaFuncMenu_Process(const StatusBarMenu *m, int32_t col, int32_t row)
2005  {
2006      luamenufunc_t func = g_LuaFuncPtrs[col*8 + row];
2007      const char *errmsg;
2008  
2009      Bassert(func != NULL);
2010      errmsg = func();
2011  
2012      if (errmsg == NULL)
2013      {
2014          printmessage16("Lua function executed successfully");
2015      }
2016      else
2017      {
2018          printmessage16("There were errors executing the Lua function, see OSD");
2019          OSD_Printf("Errors executing Lua function \"%s\": %s\n", m->name[col*8 + row], errmsg);
2020      }
2021  }
2022  
2023  static StatusBarMenu g_LuaFuncMenu = MENU_INITIALIZER_EMPTY("Lua functions", LuaFuncMenu_Process);
2024  
2025  void LuaFuncMenu(void)
2026  {
2027      M_EnterMainLoop(&g_LuaFuncMenu);
2028  }
2029  
2030  LUNATIC_EXTERN void LM_Register(const char *name, luamenufunc_t funcptr, const char *description)
2031  {
2032      if (name == NULL || g_numLuaFuncs == MENU_MAX_ENTRIES)
2033          return;
2034  
2035      g_LuaFuncPtrs[g_numLuaFuncs] = funcptr;
2036      M_RegisterFunction(&g_LuaFuncMenu, name, g_numLuaFuncs, description);
2037      g_numLuaFuncs++;
2038  }
2039  
2040  LUNATIC_EXTERN void LM_Clear(void)
2041  {
2042      M_Clear(&g_LuaFuncMenu);
2043  
2044      g_numLuaFuncs = 0;
2045      Bmemset(g_LuaFuncPtrs, 0, sizeof(g_LuaFuncPtrs));
2046  }
2047  #endif