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