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(§or[i].ceilingpicnum);
1732 j += correct_picnum(§or[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