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 
drawgradient(void)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 
message_common1(const char * tmpstr)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 
message(const char * fmt,...)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 
silentmessage(const char * fmt,...)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 
tstrtoupper(char * s)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 
taglab_init()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 
taglab_load(const char * filename,int32_t flags)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 
taglab_save(const char * mapname)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 
taglab_add(const char * label,int16_t tag)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 
taglab_getlabel(int16_t tag)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 
taglab_gettag(const char * label)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 
try_match_with_prev(int32_t idx,int32_t numsthgs,uintptr_t crc)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 
create_compressed_block(int32_t idx,const void * srcdata,uint32_t size,uintptr_t crc)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 
free_self_and_successors(mapundo_t * mapst)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!
create_map_snapshot(void)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 
map_undoredo_free(void)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 
map_undoredo(int dir)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
walls_have_equal_endpoints(int32_t w1,int32_t w2)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 
correct_yax_nextwall(int32_t wallnum,int32_t bunchnum,int32_t cf,int32_t tryfixingp)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
walls_are_consistent(int32_t w1,int32_t w2)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 
suggest_nextsector_correction(int32_t nw,int32_t j)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 
do_nextsector_correction(int32_t nw,int32_t j)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
check_spritelist_consistency()737 static int32_t check_spritelist_consistency()
738 {
739     int32_t ournumsprites=0;
740     static uint8_t havesprite[(MAXSPRITES+7)>>3];
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, (Numsprites+7)>>3);
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.
determine_outer_loop(int32_t sectnum)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 
CheckMapCorruption(int32_t printfromlev,uint64_t tryfixing)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((numwalls+7)>>3,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
M_Clear(StatusBarMenu * m)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 
M_HaveDescription(StatusBarMenu * m)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.)
M_UnregisterFunction(StatusBarMenu * m,intptr_t auxdata)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 
M_RegisterFunction(StatusBarMenu * m,const char * name,intptr_t auxdata,const char * description)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 
M_DisplayInitial(const StatusBarMenu * m)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 
M_EnterMainLoop(StatusBarMenu * m)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 
FuncMenu(void)1687 void FuncMenu(void)
1688 {
1689     M_EnterMainLoop(&g_specialFuncMenu);
1690 }
1691 
registerMenuFunction(const char * funcname,int32_t stateidx)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 
correct_picnum(int16_t * picnumptr)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 
FuncMenu_Process(const StatusBarMenu * m,int32_t col,int32_t row)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 
m32_showmouse()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 
LuaFuncMenu_Process(const StatusBarMenu * m,int32_t col,int32_t row)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 
LuaFuncMenu(void)2025 void LuaFuncMenu(void)
2026 {
2027     M_EnterMainLoop(&g_LuaFuncMenu);
2028 }
2029 
LM_Register(const char * name,luamenufunc_t funcptr,const char * description)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 
LM_Clear(void)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
2048