/* * 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-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. */ /* * * Functions to dump text description of mine. * An editor-only function, called at mine load time. * To be read by a human to verify the correctness and completeness of a mine. * */ #include #include #include #include #include #include #include "pstypes.h" #include "console.h" #include "physfsx.h" #include "gr.h" #include "palette.h" #include "fmtcheck.h" #include "inferno.h" #include "dxxerror.h" #include "object.h" #include "wall.h" #include "gameseg.h" #include "robot.h" #include "newmenu.h" #include "textures.h" #include "text.h" #include "bm.h" #include "switch.h" #include "fuelcen.h" #include "powerup.h" #include "polyobj.h" #include "gamesave.h" #include "piggy.h" #include "compiler-range_for.h" #include "d_enumerate.h" #include "d_levelstate.h" #include "d_underlying_value.h" #include "d_zip.h" #include "segiter.h" #if DXX_USE_EDITOR namespace dsx { namespace { #if defined(DXX_BUILD_DESCENT_I) using perm_tmap_buffer_type = std::array; using level_tmap_buffer_type = std::array; using wall_buffer_type = std::array; #elif defined(DXX_BUILD_DESCENT_II) using perm_tmap_buffer_type = std::array; using level_tmap_buffer_type = std::array; using wall_buffer_type = std::array; #endif } } namespace dcx { const std::array Wall_names{{ "NORMAL ", "BLASTABLE", "DOOR ", "ILLUSION ", "OPEN ", "CLOSED ", "EXTERNAL " }}; } namespace { static void dump_used_textures_level(PHYSFS_File *my_file, int level_num, const char *Gamesave_current_filename); static void say_totals(fvcobjptridx &vcobjptridx, PHYSFS_File *my_file, const char *level_name); } namespace dsx { namespace { const std::array Object_type_names{{ "WALL ", "FIREBALL", "ROBOT ", "HOSTAGE ", "PLAYER ", "WEAPON ", "CAMERA ", "POWERUP ", "DEBRIS ", "CNTRLCEN", "FLARE ", "CLUTTER ", "GHOST ", "LIGHT ", "COOP ", #if defined(DXX_BUILD_DESCENT_II) "MARKER ", #endif }}; // ---------------------------------------------------------------------------- static const char *object_types(const object_base &objp) { const auto type = objp.type; assert(type == OBJ_NONE || type < MAX_OBJECT_TYPES); return &Object_type_names[type][0]; } } } namespace { // ---------------------------------------------------------------------------- static const char *object_ids(const object_base &objp) { switch (objp.type) { case OBJ_ROBOT: return Robot_names[get_robot_id(objp)].data(); case OBJ_POWERUP: return Powerup_names[get_powerup_id(objp)].data(); default: return nullptr; } } static void err_puts(PHYSFS_File *f, const std::span str) { ++Errors_in_mine; con_puts(CON_CRITICAL, str.data(), str.size()); PHYSFSX_puts(f, str); } template static void err_puts_literal(PHYSFS_File *f, const char (&str)[len]) { err_puts(f, {str, len - 1}); } void err_printf(PHYSFS_File *my_file, const char * format) = delete; __attribute_format_printf(2, 3) static void err_printf(PHYSFS_File *my_file, const char * format, ... ) { va_list args; char message[256]; va_start(args, format ); const std::size_t len = std::max(vsnprintf(message, sizeof(message), format, args), 0); va_end(args); err_puts(my_file, {message, len}); } static void warning_puts(PHYSFS_File *f, const std::span str) { con_puts(CON_URGENT, str.data(), str.size()); PHYSFSX_puts(f, str); } void warning_printf(PHYSFS_File *my_file, const char *format) = delete; static void warning_printf(PHYSFS_File *my_file, const char * format, ... ) __attribute_format_printf(2, 3); static void warning_printf(PHYSFS_File *my_file, const char * format, ... ) { va_list args; char message[256]; va_start(args, format ); const std::size_t written = std::max(vsnprintf(message, sizeof(message), format, args), 0); va_end(args); warning_puts(my_file, {message, written}); } } // ---------------------------------------------------------------------------- namespace dsx { namespace { static void write_exit_text(fvcsegptridx &vcsegptridx, fvcwallptridx &vcwallptridx, PHYSFS_File *my_file) { int count; PHYSFSX_puts_literal(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_puts_literal(my_file, "Exit stuff\n"); // ---------- Find exit triggers ---------- count=0; auto &Triggers = LevelUniqueWallSubsystemState.Triggers; auto &vctrgptridx = Triggers.vcptridx; range_for (const auto &&t, vctrgptridx) { if (trigger_is_exit(t)) { int count2; const auto i = t.get_unchecked_index(); PHYSFSX_printf(my_file, "Trigger %2i, is an exit trigger with %i links.\n", underlying_value(i), t->num_links); count++; if (t->num_links != 0) err_printf(my_file, "Error: Exit triggers must have 0 links, this one has %i links.", t->num_links); // Find wall pointing to this trigger. count2 = 0; range_for (const auto &&w, vcwallptridx) { if (w->trigger == i) { count2++; PHYSFSX_printf(my_file, "Exit trigger %i is in segment %i, on side %i, bound to wall %hu\n", underlying_value(i), w->segnum, underlying_value(w->sidenum), underlying_value(wallnum_t{w})); } } if (count2 == 0) err_printf(my_file, "Error: Trigger %i is not bound to any wall.", underlying_value(i)); else if (count2 > 1) err_printf(my_file, "Error: Trigger %i is bound to %i walls.", underlying_value(i), count2); } } if (count == 0) err_puts_literal(my_file, "Error: No exit trigger in this mine."); else if (count != 1) err_puts_literal(my_file, "Error: More than one exit trigger in this mine."); else PHYSFSX_puts_literal(my_file, "\n"); // ---------- Find exit doors ---------- count = 0; range_for (const auto &&segp, vcsegptridx) { for (const auto &&[sidenum, child_segnum] : enumerate(segp->shared_segment::children)) { if (child_segnum == segment_exit) { PHYSFSX_printf(my_file, "Segment %3hu, side %u is an exit door.\n", segp.get_unchecked_index(), underlying_value(sidenum)); count++; } } } if (count == 0) err_puts_literal(my_file, "Error: No external wall in this mine."); else if (count != 1) { #if defined(DXX_BUILD_DESCENT_I) warning_printf(my_file, "Warning: %i external walls in this mine.", count); warning_printf(my_file, "(If %i are secret exits, then no problem.)", count-1); #endif } else PHYSFSX_puts_literal(my_file, "\n"); } } } namespace { class key_stat { const char *const label; unsigned wall_count = 0, powerup_count = 0; segnum_t seg = segment_none; sidenum_t side{}; public: key_stat(const char *const p) : label(p) { } void check_wall(const segment_array &segments, PHYSFS_File *const fp, const vcwallptridx_t wpi, const wall_key key) { auto &w = *wpi; if (!(w.keys & key)) return; PHYSFSX_printf(fp, "Wall %hu (seg=%i, side=%i) is keyed to the %s key.\n", underlying_value(wallnum_t{wpi}), w.segnum, underlying_value(w.sidenum), label); if (seg == segment_none) { seg = w.segnum; side = w.sidenum; } else { const auto &&connect_side = find_connect_side(segments.vcptridx(w.segnum), segments.vcptr(seg)); if (connect_side == side) return; warning_printf(fp, "Warning: This door at seg %i, is different than the one at seg %i, side %i", w.segnum, seg, underlying_value(side)); } ++wall_count; } void report_walls(PHYSFS_File *const fp) const { if (wall_count > 1) warning_printf(fp, "Warning: %i doors are keyed to the %s key.", wall_count, label); } void found_powerup(const unsigned amount = 1) { powerup_count += amount; } void report_keys(PHYSFS_File *const fp) const { if (!powerup_count) { if (wall_count) err_printf(fp, "Error: There is a door keyed to the %s key, but no %s key!", label, label); } else if (powerup_count > 1) err_printf(fp, "Error: There are %i %s keys!", powerup_count, label); } }; // ---------------------------------------------------------------------------- static void write_key_text(fvcobjptridx &vcobjptridx, segment_array &segments, fvcwallptridx &vcwallptridx, PHYSFS_File *my_file) { PHYSFSX_puts_literal(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_puts_literal(my_file, "Key stuff:\n"); key_stat blue("blue"), gold("gold"), red("red"); range_for (const auto &&w, vcwallptridx) { blue.check_wall(segments, my_file, w, wall_key::blue); gold.check_wall(segments, my_file, w, wall_key::gold); red.check_wall(segments, my_file, w, wall_key::red); } blue.report_walls(my_file); gold.report_walls(my_file); red.report_walls(my_file); range_for (const auto &&objp, vcobjptridx) { if (objp->type == OBJ_POWERUP) { const char *color; const auto id = get_powerup_id(objp); if ( (id == POW_KEY_BLUE && (blue.found_powerup(), color = "BLUE", true)) || (id == POW_KEY_RED && (red.found_powerup(), color = "RED", true)) || (id == POW_KEY_GOLD && (gold.found_powerup(), color = "GOLD", true)) ) { PHYSFSX_printf(my_file, "The %s key is object %hu in segment %i\n", color, static_cast(objp), objp->segnum); } } if (const auto contains_count = objp->contains_count) { if (objp->contains_type == OBJ_POWERUP) { const char *color; const auto id = objp->contains_id; if ( (id == POW_KEY_BLUE && (blue.found_powerup(contains_count), color = "BLUE", true)) || (id == POW_KEY_RED && (red.found_powerup(contains_count), color = "RED", true)) || (id == POW_KEY_GOLD && (gold.found_powerup(contains_count), color = "GOLD", true)) ) PHYSFSX_printf(my_file, "The %s key is contained in object %hu (a %s %s) in segment %hu\n", color, static_cast(objp), object_types(objp), Robot_names[get_robot_id(objp)].data(), objp->segnum); } } } blue.report_keys(my_file); gold.report_keys(my_file); red.report_keys(my_file); } // ---------------------------------------------------------------------------- static void write_control_center_text(fvcsegptridx &vcsegptridx, PHYSFS_File *my_file) { auto &Objects = LevelUniqueObjectState.Objects; auto &vcobjptridx = Objects.vcptridx; int count, count2; PHYSFSX_puts_literal(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_puts_literal(my_file, "Control Center stuff:\n"); count = 0; range_for (const auto &&segp, vcsegptridx) { if (segp->special == segment_special::controlcen) { count++; PHYSFSX_printf(my_file, "Segment %3hu is a control center.\n", static_cast(segp)); count2 = 0; range_for (const object &objp, objects_in(segp, vcobjptridx, vcsegptr)) { if (objp.type == OBJ_CNTRLCEN) count2++; } if (count2 == 0) PHYSFSX_puts_literal(my_file, "No control center object in control center segment.\n"); else if (count2 != 1) PHYSFSX_printf(my_file, "%i control center objects in control center segment.\n", count2); } } if (count == 0) err_puts_literal(my_file, "Error: No control center in this mine."); else if (count != 1) err_puts_literal(my_file, "Error: More than one control center in this mine."); } // ---------------------------------------------------------------------------- static void write_fuelcen_text(PHYSFS_File *my_file) { auto &Station = LevelUniqueFuelcenterState.Station; PHYSFSX_puts_literal(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_puts_literal(my_file, "Fuel Center stuff: (Note: This means fuel, repair, materialize, control centers!)\n"); const auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters; for (auto &&[i, f] : enumerate(partial_const_range(Station, Num_fuelcenters))) { PHYSFSX_printf(my_file, "Fuelcenter %u: Type=%i (%s), segment = %3i\n", underlying_value(i), underlying_value(f.Type), Special_names[f.Type], f.segnum); if (Segments[f.segnum].special != f.Type) err_printf(my_file, "Error: Conflicting data: Segment %i has special type %i (%s), expected to be %i", f.segnum, underlying_value(Segments[f.segnum].special), Special_names[Segments[f.segnum].special], underlying_value(f.Type)); } } // ---------------------------------------------------------------------------- static void write_segment_text(fvcsegptridx &vcsegptridx, PHYSFS_File *my_file) { auto &Objects = LevelUniqueObjectState.Objects; auto &vcobjptridx = Objects.vcptridx; PHYSFSX_puts_literal(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_puts_literal(my_file, "Segment stuff:\n"); range_for (const auto &&segp, vcsegptridx) { PHYSFSX_printf(my_file, "Segment %4hu:", static_cast(segp)); if (segp->special != segment_special::nothing) PHYSFSX_printf(my_file, " special = %3i (%s), station_idx=%3u", underlying_value(segp->special), Special_names[segp->special], underlying_value(segp->station_idx)); if (segp->matcen_num != materialization_center_number::None) PHYSFSX_printf(my_file, " matcen = %3i", underlying_value(segp->matcen_num)); PHYSFSX_puts_literal(my_file, "\n"); } range_for (const auto &&segp, vcsegptridx) { int depth; PHYSFSX_printf(my_file, "Segment %4hu: ", static_cast(segp)); depth=0; PHYSFSX_puts_literal(my_file, "Objects: "); range_for (const auto objp, objects_in(segp, vcobjptridx, vcsegptr)) { short objnum = objp; PHYSFSX_printf(my_file, "[%8s %8s %3i] ", object_types(objp), object_ids(objp), objnum); if (depth++ > 30) { PHYSFSX_printf(my_file, "\nAborted after %i links\n", depth); break; } } PHYSFSX_puts_literal(my_file, "\n"); } } // ---------------------------------------------------------------------------- static void write_matcen_text(PHYSFS_File *my_file) { PHYSFSX_puts_literal(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_puts_literal(my_file, "Materialization centers:\n"); auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; auto &Station = LevelUniqueFuelcenterState.Station; auto &Triggers = LevelUniqueWallSubsystemState.Triggers; auto &vctrgptridx = Triggers.vcptridx; const auto Num_robot_centers = LevelSharedRobotcenterState.Num_robot_centers; for (auto &&[i, robotcen] : enumerate(partial_const_range(RobotCenters, Num_robot_centers))) { const auto fuelcen_num = robotcen.fuelcen_num; auto &station = Station[fuelcen_num]; if (station.Type != segment_special::robotmaker) { err_printf(my_file, "Error: Matcen %u corresponds to Station %u, which has type %i (%s).", underlying_value(i), underlying_value(fuelcen_num), underlying_value(station.Type), Special_names[station.Type]); continue; } int trigger_count=0; const auto segnum = station.segnum; PHYSFSX_printf(my_file, "FuelCenter[%02i].Segment = %04i ", underlying_value(i), segnum); PHYSFSX_printf(my_file, "Segment[%04i].matcen_num = %02i ", segnum, underlying_value(Segments[segnum].matcen_num)); // Find trigger for this materialization center. range_for (auto &&t, vctrgptridx) { if (trigger_is_matcen(t)) { range_for (auto &k, partial_const_range(t->seg, t->num_links)) if (k == segnum) { PHYSFSX_printf(my_file, "Trigger = %2i ", underlying_value(t.get_unchecked_index())); trigger_count++; } } } PHYSFSX_puts_literal(my_file, "\n"); if (trigger_count == 0) err_printf(my_file, "Error: Matcen %i in segment %i has no trigger!", underlying_value(i), segnum); } } } // ---------------------------------------------------------------------------- namespace dsx { namespace { static void write_wall_text(fvcsegptridx &vcsegptridx, fvcwallptridx &vcwallptridx, PHYSFS_File *my_file) { enumerated_array wall_flags; PHYSFSX_puts_literal(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_puts_literal(my_file, "Walls:\n"); #if defined(DXX_BUILD_DESCENT_II) auto &Triggers = LevelUniqueWallSubsystemState.Triggers; #endif range_for (auto &&wp, vcwallptridx) { auto &w = *wp; const auto i = underlying_value(wallnum_t{wp}); PHYSFSX_printf(my_file, "Wall %03hu: seg=%3i, side=%2i, linked_wall=%3hu, type=%s, flags=%4x, hps=%3i, trigger=%2i, clip_num=%2i, keys=%2i, state=%i\n", i, w.segnum, underlying_value(w.sidenum), underlying_value(wallnum_t{w.linked_wall}), Wall_names[w.type], underlying_value(w.flags), w.hps >> 16, underlying_value(w.trigger), w.clip_num, underlying_value(w.keys), underlying_value(w.state)); #if defined(DXX_BUILD_DESCENT_II) if (const auto utw = underlying_value(w.trigger); utw >= Triggers.get_count()) PHYSFSX_printf(my_file, "Wall %03hu points to invalid trigger %u\n", i, utw); #endif auto segnum = w.segnum; const auto sidenum = w.sidenum; if (const auto actual_wall_num = Segments[segnum].shared_segment::sides[sidenum].wall_num; actual_wall_num != wp) err_printf(my_file, "Error: Wall %hu points at segment %i, side %i, but that segment doesn't point back (it's wall_num = %hi)", i, segnum, underlying_value(sidenum), underlying_value(actual_wall_num)); } wall_flags = {}; range_for (const auto &&segp, vcsegptridx) { for (const auto &&[idx, value] : enumerate(segp->shared_segment::sides)) { const auto sidep = &value; if (sidep->wall_num != wall_none) { if (auto &wf = wall_flags[sidep->wall_num]) err_printf(my_file, "Error: Wall %hu appears in two or more segments, including segment %hu, side %u.", underlying_value(sidep->wall_num), segp.get_unchecked_index(), underlying_value(idx)); else wf = 1; } } } } // ---------------------------------------------------------------------------- static void write_player_text(fvcobjptridx &vcobjptridx, PHYSFS_File *my_file) { int num_players=0; PHYSFSX_puts_literal(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_puts_literal(my_file, "Players:\n"); range_for (const auto &&objp, vcobjptridx) { if (objp->type == OBJ_PLAYER) { num_players++; PHYSFSX_printf(my_file, "Player %2i is object #%3hu in segment #%3i.\n", get_player_id(objp), static_cast(objp), objp->segnum); } } #if defined(DXX_BUILD_DESCENT_II) if (num_players != MAX_PLAYERS) err_printf(my_file, "Error: %i player objects. %i are required.", num_players, MAX_PLAYERS); #endif if (num_players > MAX_MULTI_PLAYERS) err_printf(my_file, "Error: %i player objects. %i are required.", num_players, MAX_PLAYERS); } // ---------------------------------------------------------------------------- static void write_trigger_text(PHYSFS_File *my_file) { PHYSFSX_puts_literal(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_puts_literal(my_file, "Triggers:\n"); auto &Triggers = LevelUniqueWallSubsystemState.Triggers; auto &vctrgptridx = Triggers.vcptridx; auto &Walls = LevelUniqueWallSubsystemState.Walls; auto &vcwallptr = Walls.vcptr; range_for (auto &&t, vctrgptridx) { const auto i = static_cast(t); #if defined(DXX_BUILD_DESCENT_I) PHYSFSX_printf(my_file, "Trigger %03i: flags=%04x, value=%08x, time=%8x, num_links=%i ", underlying_value(i), t->flags, static_cast(t->value), 0, t->num_links); #elif defined(DXX_BUILD_DESCENT_II) PHYSFSX_printf(my_file, "Trigger %03i: type=%02x flags=%04x, value=%08x, time=%8x, num_links=%i ", underlying_value(i), static_cast(t->type), static_cast(t->flags), t->value, 0, t->num_links); #endif for (const auto &&[seg, side] : zip(partial_range(t->seg, t->num_links), t->side)) PHYSFSX_printf(my_file, "[%03i:%i] ", seg, underlying_value(side)); // Find which wall this trigger is connected to. const auto &&we = vcwallptr.end(); const auto &&wi = std::ranges::find(vcwallptr.begin(), we, i, &wall::trigger); if (wi == we) err_printf(my_file, "Error: Trigger %i is not connected to any wall, so it can never be triggered.", underlying_value(i)); else { const auto &&w = *wi; PHYSFSX_printf(my_file, "Attached to seg:side = %i:%i, wall %hi\n", w->segnum, underlying_value(w->sidenum), underlying_value(vcsegptr(w->segnum)->shared_segment::sides[w->sidenum].wall_num)); } } } } } // ---------------------------------------------------------------------------- void write_game_text_file(const char *filename) { auto &Objects = LevelUniqueObjectState.Objects; auto &vcobjptridx = Objects.vcptridx; char my_filename[128]; int namelen; Errors_in_mine = 0; namelen = strlen(filename); Assert (namelen > 4); Assert (filename[namelen-4] == '.'); strcpy(my_filename, filename); strcpy( &my_filename[namelen-4], ".txm"); auto &&[my_file, physfserr] = PHYSFSX_openWriteBuffered(my_filename); if (!my_file) { gr_palette_load(gr_palette); nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "ERROR: Unable to open %s\n%s", my_filename, PHYSFS_getErrorByCode(physfserr)); return; } dump_used_textures_level(my_file, 0, filename); say_totals(vcobjptridx, my_file, filename); PHYSFSX_printf(my_file, "\nNumber of segments: %4i\n", Highest_segment_index+1); PHYSFSX_printf(my_file, "Number of objects: %4i\n", Objects.get_count()); auto &Walls = LevelUniqueWallSubsystemState.Walls; auto &vcwallptridx = Walls.vcptridx; PHYSFSX_printf(my_file, "Number of walls: %4i\n", Walls.get_count()); auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors; PHYSFSX_printf(my_file, "Number of open doors: %4i\n", ActiveDoors.get_count()); { auto &Triggers = LevelUniqueWallSubsystemState.Triggers; PHYSFSX_printf(my_file, "Number of triggers: %4i\n", Triggers.get_count()); } PHYSFSX_printf(my_file, "Number of matcens: %4i\n\n", LevelSharedRobotcenterState.Num_robot_centers); write_segment_text(vcsegptridx, my_file); write_fuelcen_text(my_file); write_matcen_text(my_file); write_player_text(vcobjptridx, my_file); write_wall_text(vcsegptridx, vcwallptridx, my_file); write_trigger_text(my_file); write_exit_text(vcsegptridx, vcwallptridx, my_file); // ---------- Find control center segment ---------- write_control_center_text(vcsegptridx, my_file); // ---------- Show keyed walls ---------- write_key_text(vcobjptridx, Segments, vcwallptridx, my_file); } namespace dsx { namespace { #if defined(DXX_BUILD_DESCENT_I) const char Shareware_level_names[NUM_SHAREWARE_LEVELS][12] = { "level01.rdl", "level02.rdl", "level03.rdl", "level04.rdl", "level05.rdl", "level06.rdl", "level07.rdl" }; const char Registered_level_names[NUM_REGISTERED_LEVELS][12] = { "level08.rdl", "level09.rdl", "level10.rdl", "level11.rdl", "level12.rdl", "level13.rdl", "level14.rdl", "level15.rdl", "level16.rdl", "level17.rdl", "level18.rdl", "level19.rdl", "level20.rdl", "level21.rdl", "level22.rdl", "level23.rdl", "level24.rdl", "level25.rdl", "level26.rdl", "level27.rdl", "levels1.rdl", "levels2.rdl", "levels3.rdl" }; #endif #if defined(DXX_BUILD_DESCENT_II) static int Ignore_tmap_num2_error; #endif // ---------------------------------------------------------------------------- #if defined(DXX_BUILD_DESCENT_I) #define determine_used_textures_level(load_level_flag,shareware_flag,level_num,tmap_buf,wall_buffer_type,level_tmap_buf,max_tmap) determine_used_textures_level(load_level_flag,shareware_flag,level_num,tmap_buf,wall_buffer_type,level_tmap_buf,max_tmap) #endif static void determine_used_textures_level(int load_level_flag, int shareware_flag, int level_num, perm_tmap_buffer_type &tmap_buf, wall_buffer_type &wall_buf, level_tmap_buffer_type &level_tmap_buf, int max_tmap) { #if defined(DXX_BUILD_DESCENT_II) auto &Objects = LevelUniqueObjectState.Objects; auto &vcobjptr = Objects.vcptr; #endif int j; auto &Walls = LevelUniqueWallSubsystemState.Walls; auto &WallAnims = GameSharedState.WallAnims; #if defined(DXX_BUILD_DESCENT_I) tmap_buf = {}; if (load_level_flag) { load_level(shareware_flag ? Shareware_level_names[level_num] : Registered_level_names[level_num]); } range_for (const cscusegment segp, vcsegptr) { range_for (const auto &&z, zip(segp.s.sides, segp.u.sides)) { auto &sside = std::get<0>(z); if (sside.wall_num != wall_none) { const auto clip_num = Walls.vcptr(sside.wall_num)->clip_num; if (clip_num != -1) { const auto num_frames = WallAnims[clip_num].num_frames; wall_buf[clip_num] = 1; for (j=0; j(z); const auto tmap1idx = get_texture_index(uside.tmap_num); if (tmap1idx < max_tmap) { ++ tmap_buf[tmap1idx]; if (auto &t = level_tmap_buf[tmap1idx]; t == -1) t = level_num + (!shareware_flag) * NUM_SHAREWARE_LEVELS; } else { Int3(); // Error, bogus texture map. Should not be greater than max_tmap. } if (const auto tmap_num2 = get_texture_index(uside.tmap_num2)) { if (tmap_num2 < max_tmap) { ++tmap_buf[tmap_num2]; if (level_tmap_buf[tmap_num2] == -1) level_tmap_buf[tmap_num2] = level_num + (!shareware_flag) * NUM_SHAREWARE_LEVELS; } else Int3(); // Error, bogus texture map. Should not be greater than max_tmap. } } } #elif defined(DXX_BUILD_DESCENT_II) (void)load_level_flag; (void)max_tmap; (void)shareware_flag; tmap_buf = {}; auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; // Process robots. range_for (const auto &&objp, vcobjptr) { if (objp->render_type == RT_POLYOBJ) { polymodel *po = &Polygon_models[objp->rtype.pobj_info.model_num]; for (unsigned i = 0; i < po->n_textures; ++i) { unsigned tli = ObjBitmaps[ObjBitmapPtrs[po->first_texture+i]].index; if (tli < tmap_buf.size()) { tmap_buf[tli]++; if (level_tmap_buf[tli] == -1) level_tmap_buf[tli] = level_num; } else Int3(); // Hmm, It seems this texture is bogus! } } } Ignore_tmap_num2_error = 0; // Process walls and segment sides. range_for (const csmusegment segp, vmsegptr) { range_for (const auto &&z, zip(segp.s.sides, segp.u.sides, segp.s.children)) { auto &sside = std::get<0>(z); auto &uside = std::get<1>(z); const auto child = std::get<2>(z); if (sside.wall_num != wall_none) { const auto clip_num = Walls.vcptr(sside.wall_num)->clip_num; if (clip_num != -1) { // -- int num_frames = WallAnims[clip_num].num_frames; wall_buf[clip_num] = 1; for (j=0; j<1; j++) { // Used to do through num_frames, but don't really want all the door01#3 stuff. unsigned tmap_num = Textures[WallAnims[clip_num].frames[j]].index; Assert(tmap_num < tmap_buf.size()); tmap_buf[tmap_num]++; if (level_tmap_buf[tmap_num] == -1) level_tmap_buf[tmap_num] = level_num; } } } else if (child == segment_none) { { const auto tmap1idx = get_texture_index(uside.tmap_num); if (tmap1idx < Textures.size()) { const auto ti = Textures[tmap1idx].index; assert(ti < tmap_buf.size()); ++tmap_buf[ti]; if (level_tmap_buf[ti] == -1) level_tmap_buf[ti] = level_num; } else Int3(); // Error, bogus texture map. Should not be greater than max_tmap. } if (const auto masked_tmap_num2 = get_texture_index(uside.tmap_num2)) { if (masked_tmap_num2 < Textures.size()) { const auto ti = Textures[masked_tmap_num2].index; assert(ti < tmap_buf.size()); ++tmap_buf[ti]; if (level_tmap_buf[ti] == -1) level_tmap_buf[ti] = level_num; } else { if (!Ignore_tmap_num2_error) Int3(); // Error, bogus texture map. Should not be greater than max_tmap. Ignore_tmap_num2_error = 1; uside.tmap_num2 = texture2_value::None; } } } } } #endif } } } namespace { // ---------------------------------------------------------------------------- template static void merge_buffers(std::array &dest, const std::array &src) { std::transform(dest.begin(), dest.end(), src.begin(), dest.begin(), std::plus()); } } // ---------------------------------------------------------------------------- namespace dsx { namespace { static void say_used_tmaps(PHYSFS_File *const my_file, const perm_tmap_buffer_type &tb) { int i; #if defined(DXX_BUILD_DESCENT_I) int count = 0; auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; const auto Num_tmaps = LevelUniqueTmapInfoState.Num_tmaps; for (i=0; i(TmapInfo[i].filename), tb[i], count++ >= 4 ? (count = 0, "\n") : " "); } #elif defined(DXX_BUILD_DESCENT_II) for (i = 0; i < tb.size(); ++i) if (tb[i]) { PHYSFSX_printf(my_file, "[%3i %8s (%4i)]\n", i, AllBitmaps[i].name.data(), tb[i]); } #endif } #if defined(DXX_BUILD_DESCENT_I) // ----------------------------------------------------------------------------- static void say_used_once_tmaps(PHYSFS_File *const my_file, const perm_tmap_buffer_type &tb, const level_tmap_buffer_type &tb_lnum) { int i; const char *level_name; auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; const auto Num_tmaps = LevelUniqueTmapInfoState.Num_tmaps; for (i=0; i= NUM_SHAREWARE_LEVELS) { Assert((level_num - NUM_SHAREWARE_LEVELS >= 0) && (level_num - NUM_SHAREWARE_LEVELS < NUM_REGISTERED_LEVELS)); level_name = Registered_level_names[level_num - NUM_SHAREWARE_LEVELS]; } else { Assert((level_num >= 0) && (level_num < NUM_SHAREWARE_LEVELS)); level_name = Shareware_level_names[level_num]; } PHYSFSX_printf(my_file, "Texture %3i %8s used only once on level %s\n", i, static_cast(TmapInfo[i].filename), level_name); } } #endif // ---------------------------------------------------------------------------- static void say_unused_tmaps(PHYSFS_File *my_file, perm_tmap_buffer_type &perm_tmap_buf) { #if defined(DXX_BUILD_DESCENT_I) const unsigned bound = LevelUniqueTmapInfoState.Num_tmaps; auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; auto &tmap_name_source = TmapInfo; #elif defined(DXX_BUILD_DESCENT_II) const unsigned bound = MAX_BITMAP_FILES; auto &tmap_name_source = AllBitmaps; #endif unsigned count = 0; for (auto &&[i, tb, texture, tmap_name] : enumerate(zip(partial_range(perm_tmap_buf, bound), Textures, tmap_name_source))) if (!tb) { const char usage_indicator = (GameBitmaps[texture.index].bm_data == bogus_data.data()) ? 'U' : ' '; #if defined(DXX_BUILD_DESCENT_I) const auto filename = static_cast(tmap_name.filename); #elif defined(DXX_BUILD_DESCENT_II) const auto filename = tmap_name.name.data(); #endif const char sep = (count++ >= 4) ? (count = 0, '\n') : ' '; PHYSFSX_printf(my_file, "%c[%3" PRIuFAST32 " %8s]%c", usage_indicator, i, filename, sep); } } #if defined(DXX_BUILD_DESCENT_I) // ---------------------------------------------------------------------------- static void say_unused_walls(PHYSFS_File *my_file, const wall_buffer_type &tb) { int i; for (i=0; i used_objects; while (objects_processed < Highest_object_index+1) { int objtype, objid, objcount, min_obj_val; // Find new min objnum. min_obj_val = 0x7fff0000; const object_base *min_objp = nullptr; range_for (const auto &&objp, vcobjptridx) { if (!used_objects[objp] && objp->type != OBJ_NONE) { const auto cur_obj_val = (objp->type << 10) + objp->id; if (cur_obj_val < min_obj_val) { min_objp = &*objp; min_obj_val = cur_obj_val; } } } if (!min_objp || min_objp->type == OBJ_NONE) break; objcount = 0; objtype = min_objp->type; objid = min_objp->id; range_for (const auto &&objp, vcobjptridx) { if (auto &&uo = used_objects[objp]) { } else { if ((objp->type == objtype && objp->id == objid) || (objp->type == objtype && objtype == OBJ_PLAYER) || (objp->type == objtype && objtype == OBJ_COOP) || (objp->type == objtype && objtype == OBJ_HOSTAGE)) { if (objp->type == OBJ_ROBOT) total_robots++; uo = true; objcount++; objects_processed++; } } } if (objcount) { PHYSFSX_printf(my_file, "Object: %8s %8s %3i\n", object_types(*min_objp), object_ids(*min_objp), objcount); } } PHYSFSX_printf(my_file, "Total robots = %3i\n", total_robots); } } namespace dsx { namespace { // ---------------------------------------------------------------------------- static void say_totals_all(void) { auto &&[my_file, physfserr] = PHYSFSX_openWriteBuffered("levels.all"); if (!my_file) { gr_palette_load(gr_palette); nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "ERROR: Unable to open levels.all\n%s", PHYSFS_getErrorByCode(physfserr)); return; } #if defined(DXX_BUILD_DESCENT_I) auto &Objects = LevelUniqueObjectState.Objects; auto &vcobjptridx = Objects.vcptridx; for (unsigned i = 0; i < NUM_SHAREWARE_LEVELS; ++i) { load_level(Shareware_level_names[i]); say_totals(vcobjptridx, my_file, Shareware_level_names[i]); } for (unsigned i = 0; i < NUM_REGISTERED_LEVELS; ++i) { load_level(Registered_level_names[i]); say_totals(vcobjptridx, my_file, Registered_level_names[i]); } #endif } } } namespace { static void dump_used_textures_level(PHYSFS_File *my_file, int level_num, const char *const Gamesave_current_filename) { perm_tmap_buffer_type temp_tmap_buf; level_tmap_buffer_type level_tmap_buf; level_tmap_buf.fill(-1); wall_buffer_type temp_wall_buf; determine_used_textures_level(0, 1, level_num, temp_tmap_buf, temp_wall_buf, level_tmap_buf, level_tmap_buf.size()); PHYSFSX_printf(my_file, "\nTextures used in [%s]\n", Gamesave_current_filename); say_used_tmaps(my_file, temp_tmap_buf); } } // ---------------------------------------------------------------------------- namespace dsx { void dump_used_textures_all(void) { say_totals_all(); auto &&[my_file, physfserr] = PHYSFSX_openWriteBuffered("textures.dmp"); if (!my_file) { gr_palette_load(gr_palette); nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "ERROR: Can't open textures.dmp\n%s", PHYSFS_getErrorByCode(physfserr)); return; } perm_tmap_buffer_type perm_tmap_buf{}; level_tmap_buffer_type level_tmap_buf; level_tmap_buf.fill(-1); #if defined(DXX_BUILD_DESCENT_I) perm_tmap_buffer_type temp_tmap_buf; wall_buffer_type perm_wall_buf{}; for (unsigned i = 0; i < NUM_SHAREWARE_LEVELS; ++i) { wall_buffer_type temp_wall_buf; determine_used_textures_level(1, 1, i, temp_tmap_buf, temp_wall_buf, level_tmap_buf, level_tmap_buf.size()); PHYSFSX_printf(my_file, "\nTextures used in [%s]\n", Shareware_level_names[i]); say_used_tmaps(my_file, temp_tmap_buf); merge_buffers(perm_tmap_buf, temp_tmap_buf); merge_buffers(perm_wall_buf, temp_wall_buf); } PHYSFSX_puts_literal(my_file, "\n\nUsed textures in all shareware mines:\n"); say_used_tmaps(my_file, perm_tmap_buf); PHYSFSX_puts_literal(my_file, "\nUnused textures in all shareware mines:\n"); say_unused_tmaps(my_file, perm_tmap_buf); PHYSFSX_puts_literal(my_file, "\nTextures used exactly once in all shareware mines:\n"); say_used_once_tmaps(my_file, perm_tmap_buf, level_tmap_buf); PHYSFSX_puts_literal(my_file, "\nWall anims (eg, doors) unused in all shareware mines:\n"); say_unused_walls(my_file, perm_wall_buf); for (unsigned i = 0; i < NUM_REGISTERED_LEVELS; ++i) { wall_buffer_type temp_wall_buf; determine_used_textures_level(1, 0, i, temp_tmap_buf, temp_wall_buf, level_tmap_buf, level_tmap_buf.size()); PHYSFSX_printf(my_file, "\nTextures used in [%s]\n", Registered_level_names[i]); say_used_tmaps(my_file, temp_tmap_buf); merge_buffers(perm_tmap_buf, temp_tmap_buf); } #endif PHYSFSX_puts_literal(my_file, "\n\nUsed textures in all (including registered) mines:\n"); say_used_tmaps(my_file, perm_tmap_buf); PHYSFSX_puts_literal(my_file, "\nUnused textures in all (including registered) mines:\n"); say_unused_tmaps(my_file, perm_tmap_buf); } } #endif