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