/* * 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 "pstypes.h" #include "console.h" #include "physfsx.h" #include "key.h" #include "gr.h" #include "palette.h" #include "fmtcheck.h" #include "inferno.h" #if DXX_USE_EDITOR #include "editor/editor.h" #endif #include "dxxerror.h" #include "object.h" #include "wall.h" #include "gamemine.h" #include "gameseg.h" #include "robot.h" #include "player.h" #include "newmenu.h" #include "textures.h" #include "bm.h" #include "menu.h" #include "switch.h" #include "fuelcen.h" #include "powerup.h" #include "gameseq.h" #include "polyobj.h" #include "gamesave.h" #include "piggy.h" #include "compiler-range_for.h" #include "partial_range.h" #include "segiter.h" #if DXX_USE_EDITOR namespace dsx { namespace { #if defined(DXX_BUILD_DESCENT_I) using perm_tmap_buffer_type = array; using level_tmap_buffer_type = array; using wall_buffer_type = array; #elif defined(DXX_BUILD_DESCENT_II) using perm_tmap_buffer_type = array; using level_tmap_buffer_type = array; using wall_buffer_type = array; #endif } } namespace dcx { const array Wall_names{{ "NORMAL ", "BLASTABLE", "DOOR ", "ILLUSION ", "OPEN ", "CLOSED ", "EXTERNAL " }}; } static void dump_used_textures_level(PHYSFS_File *my_file, int level_num); static void say_totals(fvcobjptridx &vcobjptridx, PHYSFS_File *my_file, const char *level_name); namespace dsx { const 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]; } } // ---------------------------------------------------------------------------- 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 char *str, size_t len) __attribute_nonnull(); static void err_puts(PHYSFS_File *f, const char *str, size_t len) #define err_puts(A1,S,...) (err_puts(A1,S, _dxx_call_puts_parameter2(1, ## __VA_ARGS__, strlen(S)))) { con_puts(CON_CRITICAL, str, len); PHYSFSX_puts(f, str); Errors_in_mine++; } template static void err_puts_literal(PHYSFS_File *f, const char (&str)[len]) __attribute_nonnull(); template static void err_puts_literal(PHYSFS_File *f, const char (&str)[len]) { err_puts(f, str, len); } static void err_printf(PHYSFS_File *my_file, const char * format, ... ) __attribute_format_printf(2, 3); static void err_printf(PHYSFS_File *my_file, const char * format, ... ) #define err_printf(A1,F,...) dxx_call_printf_checked(err_printf,err_puts_literal,(A1),(F),##__VA_ARGS__) { va_list args; char message[256]; va_start(args, format ); size_t len = vsnprintf(message,sizeof(message),format,args); va_end(args); err_puts(my_file, message, len); } static void warning_puts(PHYSFS_File *f, const char *str, size_t len) __attribute_nonnull(); static void warning_puts(PHYSFS_File *f, const char *str, size_t len) #define warning_puts(A1,S,...) (warning_puts(A1,S, _dxx_call_puts_parameter2(1, ## __VA_ARGS__, strlen(S)))) { con_puts(CON_URGENT, str, len); PHYSFSX_puts(f, str); } template static void warning_puts_literal(PHYSFS_File *f, const char (&str)[len]) __attribute_nonnull(); template static void warning_puts_literal(PHYSFS_File *f, const char (&str)[len]) { warning_puts(f, str, len); } 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, ... ) #define warning_printf(A1,F,...) dxx_call_printf_checked(warning_printf,warning_puts_literal,(A1),(F),##__VA_ARGS__) { va_list args; char message[256]; va_start(args, format ); vsnprintf(message,sizeof(message),format,args); va_end(args); warning_puts(my_file, message); } // ---------------------------------------------------------------------------- namespace dsx { static void write_exit_text(fvcsegptridx &vcsegptridx, fvcwallptridx &vcwallptridx, PHYSFS_File *my_file) { int j, count; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(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", 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 %i\n", i, w->segnum, w->sidenum, static_cast(w)); } } if (count2 == 0) err_printf(my_file, "Error: Trigger %i is not bound to any wall.", i); else if (count2 > 1) err_printf(my_file, "Error: Trigger %i is bound to %i walls.", i, count2); } } if (count == 0) err_printf(my_file, "Error: No exit trigger in this mine."); else if (count != 1) err_printf(my_file, "Error: More than one exit trigger in this mine."); else PHYSFSX_printf(my_file, "\n"); // ---------- Find exit doors ---------- count = 0; range_for (const auto &&segp, vcsegptridx) { for (j=0; jchildren[j] == segment_exit) { PHYSFSX_printf(my_file, "Segment %3hu, side %i is an exit door.\n", static_cast(segp), j); count++; } } if (count == 0) err_printf(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_printf(my_file, "\n"); } } namespace { class key_stat { const char *const label; unsigned wall_count = 0, powerup_count = 0; segnum_t seg = segment_none; uint8_t side = 0; 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_t key) { auto &w = *wpi; if (!(w.keys & key)) return; PHYSFSX_printf(fp, "Wall %i (seg=%i, side=%i) is keyed to the %s key.\n", static_cast(wpi), w.segnum, 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, 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_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(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, KEY_BLUE); gold.check_wall(segments, my_file, w, KEY_GOLD); red.check_wall(segments, my_file, w, 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) { int count, count2; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Control Center stuff:\n"); count = 0; range_for (const auto &&segp, vcsegptridx) { if (segp->special == SEGMENT_IS_CONTROLCEN) { count++; PHYSFSX_printf(my_file, "Segment %3hu is a control center.\n", static_cast(segp)); count2 = 0; range_for (const auto objp, objects_in(segp, vcobjptridx, vcsegptr)) { if (objp->type == OBJ_CNTRLCEN) count2++; } if (count2 == 0) PHYSFSX_printf(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_printf(my_file, "Error: No control center in this mine."); else if (count != 1) err_printf(my_file, "Error: More than one control center in this mine."); } // ---------------------------------------------------------------------------- static void write_fuelcen_text(PHYSFS_File *my_file) { int i; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Fuel Center stuff: (Note: This means fuel, repair, materialize, control centers!)\n"); for (i=0; i(segp)); if (segp->special != 0) PHYSFSX_printf(my_file, " special = %3i (%s), station_idx=%3i", segp->special, Special_names[segp->special], segp->station_idx); if (segp->matcen_num != -1) PHYSFSX_printf(my_file, " matcen = %3i", segp->matcen_num); PHYSFSX_printf(my_file, "\n"); } range_for (const auto &&segp, vcsegptridx) { int depth; PHYSFSX_printf(my_file, "Segment %4hu: ", static_cast(segp)); depth=0; PHYSFSX_printf(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_printf(my_file, "\n"); } } // ---------------------------------------------------------------------------- // This routine is bogus. It assumes that all centers are matcens, // which is not true. The setting of segnum is bogus. static void write_matcen_text(PHYSFS_File *my_file) { int i; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Materialization centers:\n"); auto &Triggers = LevelUniqueWallSubsystemState.Triggers; auto &vctrgptridx = Triggers.vcptridx; for (i=0; iseg, t->num_links)) if (k == segnum) { PHYSFSX_printf(my_file, "Trigger = %2i ", t.get_unchecked_index()); trigger_count++; } } } PHYSFSX_printf(my_file, "\n"); if (trigger_count == 0) err_printf(my_file, "Error: Matcen %i in segment %i has no trigger!", i, segnum); } } // ---------------------------------------------------------------------------- namespace dsx { static void write_wall_text(fvcsegptridx &vcsegptridx, fvcwallptridx &vcwallptridx, PHYSFS_File *my_file) { int j; array wall_flags; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Walls:\n"); #if defined(DXX_BUILD_DESCENT_II) auto &Triggers = LevelUniqueWallSubsystemState.Triggers; #endif range_for (auto &&wp, vcwallptridx) { auto &w = *wp; int sidenum; const auto i = static_cast(wp); PHYSFSX_printf(my_file, "Wall %03i: seg=%3i, side=%2i, linked_wall=%3i, type=%s, flags=%4x, hps=%3i, trigger=%2i, clip_num=%2i, keys=%2i, state=%i\n", i, w.segnum, w.sidenum, w.linked_wall, Wall_names[w.type], w.flags, w.hps >> 16, w.trigger, w.clip_num, w.keys, w.state); #if defined(DXX_BUILD_DESCENT_II) if (w.trigger >= Triggers.get_count()) PHYSFSX_printf(my_file, "Wall %03d points to invalid trigger %d\n",i,w.trigger); #endif auto segnum = w.segnum; sidenum = w.sidenum; if (Segments[segnum].shared_segment::sides[sidenum].wall_num != wp) err_printf(my_file, "Error: Wall %u points at segment %i, side %i, but that segment doesn't point back (it's wall_num = %hi)", i, segnum, sidenum, static_cast(Segments[segnum].shared_segment::sides[sidenum].wall_num)); } wall_flags = {}; range_for (const auto &&segp, vcsegptridx) { for (j=0; jshared_segment::sides[j]; if (sidep->wall_num != wall_none) { if (wall_flags[sidep->wall_num]) err_printf(my_file, "Error: Wall %hu appears in two or more segments, including segment %hu, side %i.", static_cast(sidep->wall_num), static_cast(segp), j); else wall_flags[sidep->wall_num] = 1; } } } } } // ---------------------------------------------------------------------------- namespace dsx { static void write_player_text(fvcobjptridx &vcobjptridx, PHYSFS_File *my_file) { int num_players=0; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(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); } } // ---------------------------------------------------------------------------- namespace dsx { static void write_trigger_text(PHYSFS_File *my_file) { PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(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 ", 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 ", i, t->type, t->flags, t->value, 0, t->num_links); #endif for (unsigned j = 0; j < t->num_links; ++j) PHYSFSX_printf(my_file, "[%03i:%i] ", t->seg[j], t->side[j]); // Find which wall this trigger is connected to. const auto &&we = vcwallptr.end(); const auto &&wi = std::find_if(vcwallptr.begin(), we, [i](const wall *const p) { return p->trigger == i; }); if (wi == we) err_printf(my_file, "Error: Trigger %i is not connected to any wall, so it can never be triggered.", i); else { const auto &&w = *wi; PHYSFSX_printf(my_file, "Attached to seg:side = %i:%i, wall %hi\n", w->segnum, w->sidenum, static_cast(vcsegptr(w->segnum)->shared_segment::sides[w->sidenum].wall_num)); } } } } // ---------------------------------------------------------------------------- void write_game_text_file(const char *filename) { 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 = PHYSFSX_openWriteBuffered(my_filename); if (!my_file) { gr_palette_load(gr_palette); nm_messagebox( NULL, 1, "Ok", "ERROR: Unable to open %s\nErrno = %i", my_filename, errno); return; } dump_used_textures_level(my_file, 0); say_totals(vcobjptridx, my_file, Gamesave_current_filename); PHYSFSX_printf(my_file, "\nNumber of segments: %4i\n", Highest_segment_index+1); PHYSFSX_printf(my_file, "Number of objects: %4i\n", Highest_object_index+1); 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", Num_robot_centers); PHYSFSX_printf(my_file, "\n"); 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); } #if defined(DXX_BUILD_DESCENT_II) // Adam: Change NUM_ADAM_LEVELS to the number of levels. #define NUM_ADAM_LEVELS 30 // Adam: Stick the names here. constexpr char Adam_level_names[NUM_ADAM_LEVELS][13] = { "D2LEVA-1.LVL", "D2LEVA-2.LVL", "D2LEVA-3.LVL", "D2LEVA-4.LVL", "D2LEVA-S.LVL", "D2LEVB-1.LVL", "D2LEVB-2.LVL", "D2LEVB-3.LVL", "D2LEVB-4.LVL", "D2LEVB-S.LVL", "D2LEVC-1.LVL", "D2LEVC-2.LVL", "D2LEVC-3.LVL", "D2LEVC-4.LVL", "D2LEVC-S.LVL", "D2LEVD-1.LVL", "D2LEVD-2.LVL", "D2LEVD-3.LVL", "D2LEVD-4.LVL", "D2LEVD-S.LVL", "D2LEVE-1.LVL", "D2LEVE-2.LVL", "D2LEVE-3.LVL", "D2LEVE-4.LVL", "D2LEVE-S.LVL", "D2LEVF-1.LVL", "D2LEVF-2.LVL", "D2LEVF-3.LVL", "D2LEVF-4.LVL", "D2LEVF-S.LVL", }; static int Ignore_tmap_num2_error; #endif // ---------------------------------------------------------------------------- namespace dsx { #if defined(DXX_BUILD_DESCENT_I) #define determine_used_textures_level(LevelSharedDestructibleLightState,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(d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, 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) { int sidenum; int j; auto &Walls = LevelUniqueWallSubsystemState.Walls; #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 auto &&segp, vcsegptr) { for (sidenum=0; sidenumshared_segment::sides[sidenum]; 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; junique_segment::sides[sidenum]; if (uside.tmap_num >= 0) { if (uside.tmap_num < max_tmap) { tmap_buf[uside.tmap_num]++; if (level_tmap_buf[uside.tmap_num] == -1) level_tmap_buf[uside.tmap_num] = 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 = uside.tmap_num2 & 0x3fff) { 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)max_tmap; (void)shareware_flag; tmap_buf = {}; if (load_level_flag) { load_level(LevelSharedDestructibleLightState, Adam_level_names[level_num]); } 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 auto &&segp, vmsegptr) { for (sidenum=0; sidenumshared_segment::sides[sidenum]; auto &uside = segp->unique_segment::sides[sidenum]; 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 (segp->children[sidenum] == segment_none) { if (uside.tmap_num >= 0) { if (uside.tmap_num < Textures.size()) { const auto ti = Textures[uside.tmap_num].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 = (uside.tmap_num2 & 0x3fff)) { 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 = 0; } } } } } #endif } } // ---------------------------------------------------------------------------- template static void merge_buffers(array &dest, const array &src) { std::transform(dest.begin(), dest.end(), src.begin(), dest.begin(), std::plus()); } // ---------------------------------------------------------------------------- namespace dsx { 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; for (i=0; i(TmapInfo[i].filename), tb[i]); if (count++ >= 4) { PHYSFSX_printf(my_file, "\n"); count = 0; } } #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; 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 // ---------------------------------------------------------------------------- namespace dsx { static void say_unused_tmaps(PHYSFS_File *my_file, perm_tmap_buffer_type &tb) { int i; int count = 0; #if defined(DXX_BUILD_DESCENT_I) const unsigned bound = Num_tmaps; auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; #elif defined(DXX_BUILD_DESCENT_II) const unsigned bound = MAX_BITMAP_FILES; #endif for (i=0; i < bound; i++) if (!tb[i]) { if (GameBitmaps[Textures[i].index].bm_data == bogus_data.data()) PHYSFSX_printf(my_file, "U"); else PHYSFSX_printf(my_file, " "); #if defined(DXX_BUILD_DESCENT_I) PHYSFSX_printf(my_file, "[%3i %8s] ", i, static_cast(TmapInfo[i].filename)); #elif defined(DXX_BUILD_DESCENT_II) PHYSFSX_printf(my_file, "[%3i %8s] ", i, AllBitmaps[i].name.data()); #endif if (count++ >= 4) { PHYSFSX_printf(my_file, "\n"); count = 0; } } } } #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); } #if defined(DXX_BUILD_DESCENT_II) int First_dump_level = 0; int Last_dump_level = NUM_ADAM_LEVELS-1; #endif // ---------------------------------------------------------------------------- namespace dsx { static void say_totals_all(void) { int i; auto my_file = PHYSFSX_openWriteBuffered("levels.all"); if (!my_file) { gr_palette_load(gr_palette); nm_messagebox( NULL, 1, "Ok", "ERROR: Unable to open levels.all\nErrno=%i", errno ); return; } #if defined(DXX_BUILD_DESCENT_I) for (i=0; i