/* * Portions of this file are copyright Rebirth contributors and licensed as * described in COPYING.txt. * Portions of this file are copyright Parallax Software and licensed * according to the Parallax license below. * See COPYING.txt for license details. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. */ /* * * Mine specific editing functions, such as load_mine, save_mine * */ #include #include #include #include #include #include "gr.h" #include "bm.h" // for MAX_TEXTURES #include "inferno.h" #include "segment.h" #include "editor.h" #include "editor/esegment.h" #include "wall.h" #include "dxxerror.h" #include "textures.h" #include "physfsx.h" #include "gamemine.h" #include "gamesave.h" #include "switch.h" #include "fuelcen.h" #include "compiler-range_for.h" #include "d_range.h" #include "partial_range.h" #include "d_underlying_value.h" #define REMOVE_EXT(s) (*(strchr( (s), '.' ))='\0') int New_file_format_save = 1; #if defined(DXX_BUILD_DESCENT_II) namespace dsx { namespace { // Converts descent 2 texture numbers back to descent 1 texture numbers. // Only works properly when the full Descent 1 texture set (descent.pig) is available. static texture_index convert_to_d1_tmap_num(const texture_index tmap_num) { switch (tmap_num) { case 137: return 0; // grey rock001 case 0: return 1; case 1: return 3; // rock021 case 270: return 6; // blue rock002 case 271: return 7; // yellow rock265 case 2: return 8; // rock004 case 138: return 9; // purple (devil:179) case 272: return 10; // red rock006 case 139: return 11; case 140: return 12; //devil:43 case 3: return 13; // rock014 case 4: return 14; // rock019 case 5: return 15; // rock020 case 6: return 16; case 141: return 17; case 129: return 18; case 7: return 19; case 142: return 20; case 143: return 21; case 8: return 22; case 9: return 23; case 10: return 24; case 144: return 25; //devil:35 case 11: return 26; case 12: return 27; case 145: return 28; //devil:43 //range handled by default case, returns 13..21 (- 16) case 163: return 38; //devil:27 case 147: return 39; //31 case 22: return 40; case 266: return 41; case 23: return 42; case 24: return 43; case 136: return 44; //devil:135 case 25: return 45; case 26: return 46; case 27: return 47; case 28: return 48; case 146: return 49; //devil:60 case 131: return 50; //devil:138 case 29: return 51; case 30: return 52; case 31: return 53; case 32: return 54; case 165: return 55; //devil:193 case 33: return 56; case 132: return 57; //devil:119 // range handled by default case, returns 58..88 (+ 24) case 197: return 88; //devil:15 // range handled by default case, returns 89..106 (- 25) case 167: return 132; // range handled by default case, returns 107..114 (- 26) case 148: return 141; //devil:106 case 115: return 142; case 116: return 143; case 117: return 144; case 118: return 145; case 119: return 146; case 149: return 147; case 120: return 148; case 121: return 149; case 122: return 150; case 123: return 151; case 124: return 152; case 125: return 153; // rock263 case 150: return 154; case 126: return 155; // rock269 case 200: return 156; // metl002 case 201: return 157; // metl003 case 186: return 158; //devil:227 case 190: return 159; //devil:246 case 151: return 160; case 152: return 161; //devil:206 case 202: return 162; case 203: return 163; case 204: return 164; case 205: return 165; case 206: return 166; case 153: return 167; case 154: return 168; case 155: return 169; case 156: return 170;//206; case 157: return 171;//227; case 207: return 172; case 208: return 173; case 158: return 174; case 159: return 175; // range handled by default case, returns 209..217 (+ 33) case 160: return 185; // range handled by default case, returns 218..224 (+ 32) case 161: return 193; case 162: return 194;//206; case 166: return 195; case 225: return 196; case 226: return 197; case 193: return 198; case 168: return 199; //devil:204 case 169: return 200; //devil:204 case 227: return 201; case 170: return 202; //devil:227 // range handled by default case, returns 228..234 (+ 25) case 171: return 210; //devil:242 case 172: return 211; //devil:240 // range handled by default case, returns 235..242 (+ 23) case 173: return 220; //devil:240 case 243: return 221; case 244: return 222; case 174: return 223; case 245: return 224; case 246: return 225; case 164: return 226;//247; matching names but not matching textures case 179: return 227; //devil:181 case 196: return 228;//248; matching names but not matching textures case 175: return 229; //devil:66 case 176: return 230; //devil:66 // range handled by default case, returns 249..257 (+ 18) case 177: return 240; //devil:132 case 130: return 241; //devil:131 case 178: return 242; //devil:15 case 180: return 243; //devil:38 case 258: return 244; case 259: return 245; case 181: return 246; // grate metl127 case 260: return 247; case 261: return 248; case 262: return 249; case 340: return 250; // white doorframe metl126 case 412: return 251; // red doorframe metl133 case 410: return 252; // blue doorframe metl134 case 411: return 253; // yellow doorframe metl135 case 263: return 254; // metl136 case 264: return 255; // metl139 case 265: return 256; // metl140 case 182: return 257;//246; brig001 case 183: return 258;//246; brig002 case 184: return 259;//246; brig003 case 185: return 260;//246; brig004 case 273: return 261; // exit01 case 274: return 262; // exit02 case 187: return 263; // ceil001 case 275: return 264; // ceil002 case 276: return 265; // ceil003 case 188: return 266; //devil:291 // range handled by default case, returns 277..291 (+ 10) case 293: return 282; case 189: return 283; case 295: return 284; case 296: return 285; case 298: return 286; // range handled by default case, returns 300..310 (+ 13) case 191: return 298; // devil:374 misc010 // range handled by default case, returns 311..326 (+ 12) case 192: return 315; // bad producer misc044 // range handled by default case, returns 327..337 (+ 11) case 352: return 327; // arw01 case 353: return 328; // misc17 case 354: return 329; // fan01 case 380: return 330; // mntr04 case 379: return 331;//373; matching names but not matching textures case 355: return 332;//344; matching names but not matching textures case 409: return 333; // lava misc11 //devil:404 case 356: return 334; // ctrl04 case 357: return 335; // ctrl01 case 358: return 336; // ctrl02 case 359: return 337; // ctrl03 case 360: return 338; // misc14 case 361: return 339; // producer misc16 case 362: return 340; // misc049 case 364: return 341; // misc060 case 363: return 342; // blown01 case 366: return 343; // misc061 case 365: return 344; case 368: return 345; case 376: return 346; case 370: return 347; case 367: return 348; case 372: return 349; case 369: return 350; case 374: return 351;//429; matching names but not matching textures case 375: return 352;//387; matching names but not matching textures case 371: return 353; case 377: return 354;//425; matching names but not matching textures case 408: return 355; case 378: return 356; // lava02 case 383: return 357;//384; matching names but not matching textures case 384: return 358;//385; matching names but not matching textures case 385: return 359;//386; matching names but not matching textures case 386: return 360; case 387: return 361; case 194: return 362; // mntr04b (devil: -1) case 388: return 363; case 391: return 364; case 392: return 365; case 393: return 366; case 394: return 367; case 395: return 368; case 396: return 369; case 195: return 370; // mntr04d (devil: -1) // range 371..584 handled by default case (wall01 and door frames) default: // ranges: if (tmap_num >= 13 && tmap_num <= 21) return tmap_num + 16; if (tmap_num >= 34 && tmap_num <= 63) return tmap_num + 24; if (tmap_num >= 64 && tmap_num <= 106) return tmap_num + 25; if (tmap_num >= 107 && tmap_num <= 114) return tmap_num + 26; if (tmap_num >= 209 && tmap_num <= 217) return tmap_num - 33; if (tmap_num >= 218 && tmap_num <= 224) return tmap_num - 32; if (tmap_num >= 228 && tmap_num <= 234) return tmap_num - 25; if (tmap_num >= 235 && tmap_num <= 242) return tmap_num - 23; if (tmap_num >= 249 && tmap_num <= 257) return tmap_num - 18; if (tmap_num >= 277 && tmap_num <= 291) return tmap_num - 10; if (tmap_num >= 300 && tmap_num <= 310) return tmap_num - 13; if (tmap_num >= 311 && tmap_num <= 326) return tmap_num - 12; if (tmap_num >= 327 && tmap_num <= 337) return tmap_num - 11; // matching names but not matching textures // wall01 and door frames: if (tmap_num > 434 && tmap_num < 731) { if (New_file_format_save) return tmap_num - 64; // d1 shareware needs special treatment: if (tmap_num < 478) return tmap_num - 68; if (tmap_num < 490) return tmap_num - 73; if (tmap_num < 537) return tmap_num - 91; if (tmap_num < 557) return tmap_num - 104; if (tmap_num < 573) return tmap_num - 111; if (tmap_num < 603) return tmap_num - 117; if (tmap_num < 635) return tmap_num - 141; if (tmap_num < 731) return tmap_num - 147; } Warning("can't convert unknown texture #%hu to descent 1.\n", tmap_num); return tmap_num; } } } } #endif namespace { static int save_mine_data(PHYSFS_File * SaveFile); // ----------------------------------------------------------------------------- // Save mine will: // 1. Write file info, header info, editor info, vertex data, segment data, // and new_segment in that order, marking their file offset. // 2. Go through all the fields and fill in the offset, size, and sizeof // values in the headers. static int med_save_mine(const char * filename) { char ErrorMessage[256]; auto &&[SaveFile, physfserr] = PHYSFSX_openWriteBuffered(filename); if (!SaveFile) { snprintf(ErrorMessage, sizeof(ErrorMessage), "ERROR: Unable to open %.200s: %s\n", filename, PHYSFS_getErrorByCode(physfserr)); ui_messagebox( -2, -2, 1, ErrorMessage, "Ok" ); return 1; } save_mine_data(SaveFile); //==================== CLOSE THE FILE ============================= return 0; } // ----------------------------------------------------------------------------- // saves to an already-open file static int save_mine_data(PHYSFS_File * SaveFile) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); int header_offset, editor_offset, vertex_offset, segment_offset, texture_offset, walls_offset, triggers_offset; //, links_offset; int newseg_verts_offset; int newsegment_offset; med_compress_mine(); warn_if_concave_segments(); std::array current_tmap_list; auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; for (int i=0;i> nbits; if (int_value > INT16_MAX) { short_value = INT16_MAX; } else if( int_value < -0x7fff ) { short_value = -0x7fff; } else short_value = static_cast(int_value); PHYSFS_writeSLE16(SaveFile, short_value); } //version of dump for unsigned values static void dump_fix_as_ushort( fix value, int nbits, PHYSFS_File *SaveFile ) { uint int_value=0; ushort short_value; if (value < 0) { Int3(); //hey---show this to Matt value = 0; } else int_value = value >> nbits; if( int_value > 0xffff ) { short_value = 0xffff; } else short_value = int_value; PHYSFS_writeULE16(SaveFile, short_value); } static void write_children(const shared_segment &seg, const unsigned bit_mask, PHYSFS_File *const SaveFile) { auto &children = seg.children; for (int bit = 0; bit < MAX_SIDES_PER_SEGMENT; bit++) { if (bit_mask & (1 << bit)) PHYSFS_writeSLE16(SaveFile, children[bit]); } } static void write_verts(const shared_segment &seg, PHYSFS_File *const SaveFile) { range_for (auto &i, seg.verts) PHYSFS_writeSLE16(SaveFile, static_cast(i)); } static void write_special(const shared_segment &seg, const unsigned bit_mask, PHYSFS_File *const SaveFile) { if (bit_mask & (1 << MAX_SIDES_PER_SEGMENT)) { PHYSFSX_writeU8(SaveFile, underlying_value(seg.special)); PHYSFSX_writeU8(SaveFile, underlying_value(seg.matcen_num)); PHYSFS_writeULE16(SaveFile, underlying_value(seg.station_idx)); } } } int med_save_mine(const mine_filename_type &filename) { return med_save_mine(filename.data()); } // ----------------------------------------------------------------------------- // saves compiled mine data to an already-open file... namespace dsx { int save_mine_data_compiled(PHYSFS_File *SaveFile) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); ubyte version = COMPILED_MINE_VERSION; ubyte bit_mask = 0; med_compress_mine(); warn_if_concave_segments(); if (Highest_segment_index >= MAX_SEGMENTS) { char message[128]; snprintf(message, sizeof(message), "Error: Too many segments (%i > %" PRIuFAST32 ") for game (not editor)", Highest_segment_index+1, static_cast(MAX_SEGMENTS)); ui_messagebox( -2, -2, 1, message, "Ok" ); } auto &Vertices = LevelSharedVertexState.get_vertices(); if (Vertices.get_count() > MAX_VERTICES) { char message[128]; snprintf(message, sizeof(message), "Error: Too many vertices (%i > %" PRIuFAST32 ") for game (not editor)", Vertices.get_count(), static_cast(MAX_VERTICES)); ui_messagebox( -2, -2, 1, message, "Ok" ); } //=============================== Writing part ============================== PHYSFSX_writeU8(SaveFile, version); // 1 byte = compiled version if (New_file_format_save) { PHYSFS_writeSLE16(SaveFile, LevelSharedVertexState.Num_vertices); // 2 bytes = Num_vertices PHYSFS_writeSLE16(SaveFile, LevelSharedSegmentState.Num_segments); // 2 bytes = Num_segments } else { PHYSFS_writeSLE32(SaveFile, LevelSharedVertexState.Num_vertices); // 4 bytes = Num_vertices PHYSFS_writeSLE32(SaveFile, LevelSharedSegmentState.Num_segments); // 4 bytes = Num_segments } range_for (auto &i, partial_const_range(Vertices, LevelSharedVertexState.Num_vertices)) PHYSFSX_writeVector(SaveFile, i); const auto Num_segments = LevelSharedSegmentState.Num_segments; for (segnum_t segnum = 0; segnum < Num_segments; segnum++) { const cscusegment &&seg = vcsegptr(segnum); for (short sidenum = 0; sidenum < MAX_SIDES_PER_SEGMENT; sidenum++) { if (seg.s.children[sidenum] != segment_none) bit_mask |= (1 << sidenum); } if (seg.s.special != segment_special::nothing || seg.s.matcen_num != materialization_center_number::None || seg.s.station_idx != station_number::None) bit_mask |= (1 << MAX_SIDES_PER_SEGMENT); if (New_file_format_save) PHYSFSX_writeU8(SaveFile, bit_mask); else bit_mask = 0x7F; if (Gamesave_current_version == 5) // d2 SHAREWARE level { write_special(seg, bit_mask, SaveFile); write_verts(seg, SaveFile); write_children(seg, bit_mask, SaveFile); } else { write_children(seg, bit_mask, SaveFile); write_verts(seg, SaveFile); if (Gamesave_current_version <= 1) // descent 1 level write_special(seg, bit_mask, SaveFile); } if (Gamesave_current_version <= 5) // descent 1 thru d2 SHAREWARE level dump_fix_as_ushort(seg.u.static_light, 4, SaveFile); // Write the walls as a 6 byte array bit_mask = 0; for (short sidenum = 0; sidenum < MAX_SIDES_PER_SEGMENT; sidenum++) { if (seg.s.sides[sidenum].wall_num != wall_none) { bit_mask |= (1 << sidenum); } } if (New_file_format_save) PHYSFSX_writeU8(SaveFile, bit_mask); else bit_mask = 0x3F; for (short sidenum = 0; sidenum < MAX_SIDES_PER_SEGMENT; sidenum++) { if (bit_mask & (1 << sidenum)) PHYSFSX_writeU8(SaveFile, underlying_value(seg.s.sides[sidenum].wall_num)); } for (short sidenum = 0; sidenum < MAX_SIDES_PER_SEGMENT; sidenum++) { if (seg.s.children[sidenum] == segment_none || seg.s.sides[sidenum].wall_num != wall_none) { auto tmap_num = seg.u.sides[sidenum].tmap_num; auto tmap_num2 = seg.u.sides[sidenum].tmap_num2; #if defined(DXX_BUILD_DESCENT_II) if (Gamesave_current_version <= 3) // convert texture numbers back to d1 { tmap_num = build_texture1_value(convert_to_d1_tmap_num(get_texture_index(tmap_num))); if (tmap_num2 != texture2_value::None) { tmap_num2 = build_texture2_value(convert_to_d1_tmap_num(get_texture_index(tmap_num2)), get_texture_rotation_high(tmap_num2)); } } #endif uint16_t write_tmap_num = static_cast(tmap_num); if (tmap_num2 != texture2_value::None && New_file_format_save) write_tmap_num |= 0x8000; PHYSFS_writeSLE16(SaveFile, write_tmap_num); if (tmap_num2 != texture2_value::None || !New_file_format_save) PHYSFS_writeSLE16(SaveFile, static_cast(tmap_num2)); range_for (auto &i, seg.u.sides[sidenum].uvls) { dump_fix_as_short(i.u, 5, SaveFile); dump_fix_as_short(i.v, 5, SaveFile); dump_fix_as_ushort(i.l, 1, SaveFile); } } } } #if defined(DXX_BUILD_DESCENT_II) if (Gamesave_current_version > 5) for (segnum_t i = 0; i < Num_segments; i++) segment2_write(vcsegptr(i), SaveFile); #endif return 0; } }