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