Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Portions of this file are copyright Rebirth contributors and licensed as
  3.  * described in COPYING.txt.
  4.  * Portions of this file are copyright Parallax Software and licensed
  5.  * according to the Parallax license below.
  6.  * See COPYING.txt for license details.
  7.  
  8. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
  9. SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
  10. END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
  11. ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
  12. IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
  13. SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
  14. FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
  15. CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
  16. AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
  17. COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
  18. */
  19.  
  20. /*
  21.  *
  22.  * Functions to dump text description of mine.
  23.  * An editor-only function, called at mine load time.
  24.  * To be read by a human to verify the correctness and completeness of a mine.
  25.  *
  26.  */
  27.  
  28. #include <bitset>
  29. #include <stdio.h>
  30. #include <cinttypes>
  31. #include <string.h>
  32. #include <stdarg.h>
  33. #include <errno.h>
  34.  
  35. #include "pstypes.h"
  36. #include "console.h"
  37. #include "physfsx.h"
  38. #include "key.h"
  39. #include "gr.h"
  40. #include "palette.h"
  41. #include "fmtcheck.h"
  42.  
  43. #include "inferno.h"
  44. #if DXX_USE_EDITOR
  45. #include "editor/editor.h"
  46. #endif
  47. #include "dxxerror.h"
  48. #include "object.h"
  49. #include "wall.h"
  50. #include "gamemine.h"
  51. #include "gameseg.h"
  52. #include "robot.h"
  53. #include "player.h"
  54. #include "newmenu.h"
  55. #include "textures.h"
  56.  
  57. #include "bm.h"
  58. #include "menu.h"
  59. #include "switch.h"
  60. #include "fuelcen.h"
  61. #include "powerup.h"
  62. #include "gameseq.h"
  63. #include "polyobj.h"
  64. #include "gamesave.h"
  65. #include "piggy.h"
  66.  
  67. #include "compiler-range_for.h"
  68. #include "d_enumerate.h"
  69. #include "d_range.h"
  70. #include "d_zip.h"
  71. #include "segiter.h"
  72.  
  73. #if DXX_USE_EDITOR
  74.  
  75. namespace dsx {
  76. namespace {
  77. #if defined(DXX_BUILD_DESCENT_I)
  78. using perm_tmap_buffer_type = std::array<int, MAX_TEXTURES>;
  79. using level_tmap_buffer_type = std::array<int8_t, MAX_TEXTURES>;
  80. using wall_buffer_type = std::array<int, MAX_WALL_ANIMS>;
  81. #elif defined(DXX_BUILD_DESCENT_II)
  82. using perm_tmap_buffer_type = std::array<int, MAX_BITMAP_FILES>;
  83. using level_tmap_buffer_type = std::array<int8_t, MAX_BITMAP_FILES>;
  84. using wall_buffer_type = std::array<int, MAX_BITMAP_FILES>;
  85. #endif
  86. }
  87. }
  88.  
  89. namespace dcx {
  90. const std::array<char[10], 7> Wall_names{{
  91.         "NORMAL   ",
  92.         "BLASTABLE",
  93.         "DOOR     ",
  94.         "ILLUSION ",
  95.         "OPEN     ",
  96.         "CLOSED   ",
  97.         "EXTERNAL "
  98. }};
  99. }
  100.  
  101. static void dump_used_textures_level(PHYSFS_File *my_file, int level_num, const char *Gamesave_current_filename);
  102. static void say_totals(fvcobjptridx &vcobjptridx, PHYSFS_File *my_file, const char *level_name);
  103.  
  104. namespace dsx {
  105. const std::array<char[9], MAX_OBJECT_TYPES> Object_type_names{{
  106.         "WALL    ",
  107.         "FIREBALL",
  108.         "ROBOT   ",
  109.         "HOSTAGE ",
  110.         "PLAYER  ",
  111.         "WEAPON  ",
  112.         "CAMERA  ",
  113.         "POWERUP ",
  114.         "DEBRIS  ",
  115.         "CNTRLCEN",
  116.         "FLARE   ",
  117.         "CLUTTER ",
  118.         "GHOST   ",
  119.         "LIGHT   ",
  120.         "COOP    ",
  121. #if defined(DXX_BUILD_DESCENT_II)
  122.         "MARKER  ",
  123. #endif
  124. }};
  125.  
  126. // ----------------------------------------------------------------------------
  127. static const char *object_types(const object_base &objp)
  128. {
  129.         const auto type = objp.type;
  130.         assert(type == OBJ_NONE || type < MAX_OBJECT_TYPES);
  131.         return &Object_type_names[type][0];
  132. }
  133. }
  134.  
  135. // ----------------------------------------------------------------------------
  136. static const char *object_ids(const object_base &objp)
  137. {
  138.         switch (objp.type)
  139.         {
  140.                 case OBJ_ROBOT:
  141.                         return Robot_names[get_robot_id(objp)].data();
  142.                 case OBJ_POWERUP:
  143.                         return Powerup_names[get_powerup_id(objp)].data();
  144.                 default:
  145.                         return nullptr;
  146.         }
  147. }
  148.  
  149. static void err_puts(PHYSFS_File *f, const char *str, size_t len) __attribute_nonnull();
  150. static void err_puts(PHYSFS_File *f, const char *str, size_t len)
  151. #define err_puts(A1,S,...)      (err_puts(A1,S, _dxx_call_puts_parameter2(1, ## __VA_ARGS__, strlen(S))))
  152. {
  153.         con_puts(CON_CRITICAL, str, len);
  154.         PHYSFSX_puts(f, str);
  155.         Errors_in_mine++;
  156. }
  157.  
  158. template <size_t len>
  159. static void err_puts_literal(PHYSFS_File *f, const char (&str)[len]) __attribute_nonnull();
  160. template <size_t len>
  161. static void err_puts_literal(PHYSFS_File *f, const char (&str)[len])
  162. {
  163.         err_puts(f, str, len);
  164. }
  165.  
  166. static void err_printf(PHYSFS_File *my_file, const char * format, ... ) __attribute_format_printf(2, 3);
  167. static void err_printf(PHYSFS_File *my_file, const char * format, ... )
  168. #define err_printf(A1,F,...)    dxx_call_printf_checked(err_printf,err_puts_literal,(A1),(F),##__VA_ARGS__)
  169. {
  170.         va_list args;
  171.         char            message[256];
  172.  
  173.         va_start(args, format );
  174.         size_t len = vsnprintf(message,sizeof(message),format,args);
  175.         va_end(args);
  176.         err_puts(my_file, message, len);
  177. }
  178.  
  179. static void warning_puts(PHYSFS_File *f, const char *str, size_t len) __attribute_nonnull();
  180. static void warning_puts(PHYSFS_File *f, const char *str, size_t len)
  181. #define warning_puts(A1,S,...)  (warning_puts(A1,S, _dxx_call_puts_parameter2(1, ## __VA_ARGS__, strlen(S))))
  182. {
  183.         con_puts(CON_URGENT, str, len);
  184.         PHYSFSX_puts(f, str);
  185. }
  186.  
  187. template <size_t len>
  188. static void warning_puts_literal(PHYSFS_File *f, const char (&str)[len]) __attribute_nonnull();
  189. template <size_t len>
  190. static void warning_puts_literal(PHYSFS_File *f, const char (&str)[len])
  191. {
  192.         warning_puts(f, str, len);
  193. }
  194.  
  195. static void warning_printf(PHYSFS_File *my_file, const char * format, ... ) __attribute_format_printf(2, 3);
  196. static void warning_printf(PHYSFS_File *my_file, const char * format, ... )
  197. #define warning_printf(A1,F,...)        dxx_call_printf_checked(warning_printf,warning_puts_literal,(A1),(F),##__VA_ARGS__)
  198. {
  199.         va_list args;
  200.         char            message[256];
  201.  
  202.         va_start(args, format );
  203.         vsnprintf(message,sizeof(message),format,args);
  204.         va_end(args);
  205.         warning_puts(my_file, message);
  206. }
  207.  
  208. // ----------------------------------------------------------------------------
  209. namespace dsx {
  210. static void write_exit_text(fvcsegptridx &vcsegptridx, fvcwallptridx &vcwallptridx, PHYSFS_File *my_file)
  211. {
  212.         int     count;
  213.  
  214.         PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
  215.         PHYSFSX_printf(my_file, "Exit stuff\n");
  216.  
  217.         //      ---------- Find exit triggers ----------
  218.         count=0;
  219.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  220.         auto &vctrgptridx = Triggers.vcptridx;
  221.         range_for (const auto &&t, vctrgptridx)
  222.         {
  223.                 if (trigger_is_exit(t))
  224.                 {
  225.                         int     count2;
  226.  
  227.                         const auto i = t.get_unchecked_index();
  228.                         PHYSFSX_printf(my_file, "Trigger %2i, is an exit trigger with %i links.\n", i, t->num_links);
  229.                         count++;
  230.                         if (t->num_links != 0)
  231.                                 err_printf(my_file, "Error: Exit triggers must have 0 links, this one has %i links.", t->num_links);
  232.  
  233.                         //      Find wall pointing to this trigger.
  234.                         count2 = 0;
  235.                         range_for (const auto &&w, vcwallptridx)
  236.                         {
  237.                                 if (w->trigger == i)
  238.                                 {
  239.                                         count2++;
  240.                                         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<wallnum_t>(w));
  241.                                 }
  242.                         }
  243.                         if (count2 == 0)
  244.                                 err_printf(my_file, "Error: Trigger %i is not bound to any wall.", i);
  245.                         else if (count2 > 1)
  246.                                 err_printf(my_file, "Error: Trigger %i is bound to %i walls.", i, count2);
  247.  
  248.                 }
  249.         }
  250.  
  251.         if (count == 0)
  252.                 err_printf(my_file, "Error: No exit trigger in this mine.");
  253.         else if (count != 1)
  254.                 err_printf(my_file, "Error: More than one exit trigger in this mine.");
  255.         else
  256.                 PHYSFSX_printf(my_file, "\n");
  257.  
  258.         //      ---------- Find exit doors ----------
  259.         count = 0;
  260.         range_for (const auto &&segp, vcsegptridx)
  261.         {
  262.                 range_for (const auto &&es, enumerate(segp->children))
  263.                 {
  264.                         if (es.value == segment_exit)
  265.                         {
  266.                                 PHYSFSX_printf(my_file, "Segment %3hu, side %" PRIuFAST32 " is an exit door.\n", segp.get_unchecked_index(), es.idx);
  267.                                 count++;
  268.                         }
  269.                 }
  270.         }
  271.  
  272.         if (count == 0)
  273.                 err_printf(my_file, "Error: No external wall in this mine.");
  274.         else if (count != 1) {
  275. #if defined(DXX_BUILD_DESCENT_I)
  276.                 warning_printf(my_file, "Warning: %i external walls in this mine.", count);
  277.                 warning_printf(my_file, "(If %i are secret exits, then no problem.)", count-1);
  278. #endif
  279.         } else
  280.                 PHYSFSX_printf(my_file, "\n");
  281. }
  282. }
  283.  
  284. namespace {
  285.  
  286. class key_stat
  287. {
  288.         const char *const label;
  289.         unsigned wall_count = 0, powerup_count = 0;
  290.         segnum_t seg = segment_none;
  291.         uint8_t side = 0;
  292. public:
  293.         key_stat(const char *const p) :
  294.                 label(p)
  295.         {
  296.         }
  297.         void check_wall(const segment_array &segments, PHYSFS_File *const fp, const vcwallptridx_t wpi, const wall_key_t key)
  298.         {
  299.                 auto &w = *wpi;
  300.                 if (!(w.keys & key))
  301.                         return;
  302.                 PHYSFSX_printf(fp, "Wall %i (seg=%i, side=%i) is keyed to the %s key.\n", static_cast<wallnum_t>(wpi), w.segnum, w.sidenum, label);
  303.                 if (seg == segment_none)
  304.                 {
  305.                         seg = w.segnum;
  306.                         side = w.sidenum;
  307.                 }
  308.                 else
  309.                 {
  310.                         const auto &&connect_side = find_connect_side(segments.vcptridx(w.segnum), segments.vcptr(seg));
  311.                         if (connect_side == side)
  312.                                 return;
  313.                         warning_printf(fp, "Warning: This door at seg %i, is different than the one at seg %i, side %i", w.segnum, seg, side);
  314.                 }
  315.                 ++wall_count;
  316.         }
  317.         void report_walls(PHYSFS_File *const fp) const
  318.         {
  319.                 if (wall_count > 1)
  320.                         warning_printf(fp, "Warning: %i doors are keyed to the %s key.", wall_count, label);
  321.         }
  322.         void found_powerup(const unsigned amount = 1)
  323.         {
  324.                 powerup_count += amount;
  325.         }
  326.         void report_keys(PHYSFS_File *const fp) const
  327.         {
  328.                 if (!powerup_count)
  329.                 {
  330.                         if (wall_count)
  331.                                 err_printf(fp, "Error: There is a door keyed to the %s key, but no %s key!", label, label);
  332.                 }
  333.                 else if (powerup_count > 1)
  334.                         err_printf(fp, "Error: There are %i %s keys!", powerup_count, label);
  335.         }
  336. };
  337.  
  338. }
  339.  
  340. // ----------------------------------------------------------------------------
  341. static void write_key_text(fvcobjptridx &vcobjptridx, segment_array &segments, fvcwallptridx &vcwallptridx, PHYSFS_File *my_file)
  342. {
  343.         PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
  344.         PHYSFSX_printf(my_file, "Key stuff:\n");
  345.  
  346.         key_stat blue("blue"), gold("gold"), red("red");
  347.  
  348.         range_for (const auto &&w, vcwallptridx)
  349.         {
  350.                 blue.check_wall(segments, my_file, w, KEY_BLUE);
  351.                 gold.check_wall(segments, my_file, w, KEY_GOLD);
  352.                 red.check_wall(segments, my_file, w, KEY_RED);
  353.         }
  354.  
  355.         blue.report_walls(my_file);
  356.         gold.report_walls(my_file);
  357.         red.report_walls(my_file);
  358.  
  359.         range_for (const auto &&objp, vcobjptridx)
  360.         {
  361.                 if (objp->type == OBJ_POWERUP)
  362.                 {
  363.                         const char *color;
  364.                         const auto id = get_powerup_id(objp);
  365.                         if (
  366.                                 (id == POW_KEY_BLUE && (blue.found_powerup(), color = "BLUE", true)) ||
  367.                                 (id == POW_KEY_RED && (red.found_powerup(), color = "RED", true)) ||
  368.                                 (id == POW_KEY_GOLD && (gold.found_powerup(), color = "GOLD", true))
  369.                         )
  370.                         {
  371.                                 PHYSFSX_printf(my_file, "The %s key is object %hu in segment %i\n", color, static_cast<objnum_t>(objp), objp->segnum);
  372.                         }
  373.                 }
  374.  
  375.                 if (const auto contains_count = objp->contains_count)
  376.                 {
  377.                         if (objp->contains_type == OBJ_POWERUP)
  378.                         {
  379.                                 const char *color;
  380.                                 const auto id = objp->contains_id;
  381.                                 if (
  382.                                         (id == POW_KEY_BLUE && (blue.found_powerup(contains_count), color = "BLUE", true)) ||
  383.                                         (id == POW_KEY_RED && (red.found_powerup(contains_count), color = "RED", true)) ||
  384.                                         (id == POW_KEY_GOLD && (gold.found_powerup(contains_count), color = "GOLD", true))
  385.                                 )
  386.                                         PHYSFSX_printf(my_file, "The %s key is contained in object %hu (a %s %s) in segment %hu\n", color, static_cast<objnum_t>(objp), object_types(objp), Robot_names[get_robot_id(objp)].data(), objp->segnum);
  387.                         }
  388.                 }
  389.         }
  390.  
  391.         blue.report_keys(my_file);
  392.         gold.report_keys(my_file);
  393.         red.report_keys(my_file);
  394. }
  395.  
  396. // ----------------------------------------------------------------------------
  397. static void write_control_center_text(fvcsegptridx &vcsegptridx, PHYSFS_File *my_file)
  398. {
  399.         auto &Objects = LevelUniqueObjectState.Objects;
  400.         auto &vcobjptridx = Objects.vcptridx;
  401.         int     count, count2;
  402.  
  403.         PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
  404.         PHYSFSX_printf(my_file, "Control Center stuff:\n");
  405.  
  406.         count = 0;
  407.         range_for (const auto &&segp, vcsegptridx)
  408.         {
  409.                 if (segp->special == SEGMENT_IS_CONTROLCEN)
  410.                 {
  411.                         count++;
  412.                         PHYSFSX_printf(my_file, "Segment %3hu is a control center.\n", static_cast<uint16_t>(segp));
  413.                         count2 = 0;
  414.                         range_for (const object &objp, objects_in(segp, vcobjptridx, vcsegptr))
  415.                         {
  416.                                 if (objp.type == OBJ_CNTRLCEN)
  417.                                         count2++;
  418.                         }
  419.                         if (count2 == 0)
  420.                                 PHYSFSX_printf(my_file, "No control center object in control center segment.\n");
  421.                         else if (count2 != 1)
  422.                                 PHYSFSX_printf(my_file, "%i control center objects in control center segment.\n", count2);
  423.                 }
  424.         }
  425.  
  426.         if (count == 0)
  427.                 err_printf(my_file, "Error: No control center in this mine.");
  428.         else if (count != 1)
  429.                 err_printf(my_file, "Error: More than one control center in this mine.");
  430. }
  431.  
  432. // ----------------------------------------------------------------------------
  433. static void write_fuelcen_text(PHYSFS_File *my_file)
  434. {
  435.         auto &Station = LevelUniqueFuelcenterState.Station;
  436.         int     i;
  437.  
  438.         PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
  439.         PHYSFSX_printf(my_file, "Fuel Center stuff: (Note: This means fuel, repair, materialize, control centers!)\n");
  440.  
  441.         const auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters;
  442.         for (i=0; i<Num_fuelcenters; i++) {
  443.                 PHYSFSX_printf(my_file, "Fuelcenter %i: Type=%i (%s), segment = %3i\n", i, Station[i].Type, Special_names[Station[i].Type], Station[i].segnum);
  444.                 if (Segments[Station[i].segnum].special != Station[i].Type)
  445.                         err_printf(my_file, "Error: Conflicting data: Segment %i has special type %i (%s), expected to be %i", Station[i].segnum, Segments[Station[i].segnum].special, Special_names[Segments[Station[i].segnum].special], Station[i].Type);
  446.         }
  447. }
  448.  
  449. // ----------------------------------------------------------------------------
  450. static void write_segment_text(fvcsegptridx &vcsegptridx, PHYSFS_File *my_file)
  451. {
  452.         auto &Objects = LevelUniqueObjectState.Objects;
  453.         auto &vcobjptridx = Objects.vcptridx;
  454.         PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
  455.         PHYSFSX_printf(my_file, "Segment stuff:\n");
  456.  
  457.         range_for (const auto &&segp, vcsegptridx)
  458.         {
  459.                 PHYSFSX_printf(my_file, "Segment %4hu:", static_cast<uint16_t>(segp));
  460.                 if (segp->special != 0)
  461.                         PHYSFSX_printf(my_file, " special = %3i (%s), station_idx=%3i", segp->special, Special_names[segp->special], segp->station_idx);
  462.                 if (segp->matcen_num != -1)
  463.                         PHYSFSX_printf(my_file, " matcen = %3i", segp->matcen_num);
  464.                 PHYSFSX_printf(my_file, "\n");
  465.         }
  466.  
  467.         range_for (const auto &&segp, vcsegptridx)
  468.         {
  469.                 int     depth;
  470.  
  471.                 PHYSFSX_printf(my_file, "Segment %4hu: ", static_cast<uint16_t>(segp));
  472.                 depth=0;
  473.                         PHYSFSX_printf(my_file, "Objects: ");
  474.                 range_for (const auto objp, objects_in(segp, vcobjptridx, vcsegptr))
  475.                         {
  476.                                 short objnum = objp;
  477.                                 PHYSFSX_printf(my_file, "[%8s %8s %3i] ", object_types(objp), object_ids(objp), objnum);
  478.                                 if (depth++ > 30) {
  479.                                         PHYSFSX_printf(my_file, "\nAborted after %i links\n", depth);
  480.                                         break;
  481.                                 }
  482.                         }
  483.                 PHYSFSX_printf(my_file, "\n");
  484.         }
  485. }
  486.  
  487. // ----------------------------------------------------------------------------
  488. // This routine is bogus.  It assumes that all centers are matcens,
  489. // which is not true.  The setting of segnum is bogus.
  490. static void write_matcen_text(PHYSFS_File *my_file)
  491. {
  492.         int     i;
  493.  
  494.         PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
  495.         PHYSFSX_printf(my_file, "Materialization centers:\n");
  496.         auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
  497.         auto &Station = LevelUniqueFuelcenterState.Station;
  498.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  499.         auto &vctrgptridx = Triggers.vcptridx;
  500.         const auto Num_robot_centers = LevelSharedRobotcenterState.Num_robot_centers;
  501.         for (i=0; i<Num_robot_centers; i++) {
  502.                 int     trigger_count=0, fuelcen_num;
  503.  
  504.                 PHYSFSX_printf(my_file, "FuelCenter[%02i].Segment = %04i  ", i, Station[i].segnum);
  505.                 PHYSFSX_printf(my_file, "Segment[%04i].matcen_num = %02i  ", Station[i].segnum, Segments[Station[i].segnum].matcen_num);
  506.  
  507.                 fuelcen_num = RobotCenters[i].fuelcen_num;
  508.                 if (Station[fuelcen_num].Type != SEGMENT_IS_ROBOTMAKER)
  509.                         err_printf(my_file, "Error: Matcen %i corresponds to Station %i, which has type %i (%s).", i, fuelcen_num, Station[fuelcen_num].Type, Special_names[Station[fuelcen_num].Type]);
  510.  
  511.                 auto segnum = Station[fuelcen_num].segnum;
  512.  
  513.                 //      Find trigger for this materialization center.
  514.                 range_for (auto &&t, vctrgptridx)
  515.                 {
  516.                         if (trigger_is_matcen(t))
  517.                         {
  518.                                 range_for (auto &k, partial_const_range(t->seg, t->num_links))
  519.                                         if (k == segnum)
  520.                                         {
  521.                                                 PHYSFSX_printf(my_file, "Trigger = %2i  ", t.get_unchecked_index());
  522.                                                 trigger_count++;
  523.                                         }
  524.                         }
  525.                 }
  526.                 PHYSFSX_printf(my_file, "\n");
  527.  
  528.                 if (trigger_count == 0)
  529.                         err_printf(my_file, "Error: Matcen %i in segment %i has no trigger!", i, segnum);
  530.  
  531.         }
  532. }
  533.  
  534. // ----------------------------------------------------------------------------
  535. namespace dsx {
  536. static void write_wall_text(fvcsegptridx &vcsegptridx, fvcwallptridx &vcwallptridx, PHYSFS_File *my_file)
  537. {
  538.         std::array<int8_t, MAX_WALLS> wall_flags;
  539.  
  540.         PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
  541.         PHYSFSX_printf(my_file, "Walls:\n");
  542. #if defined(DXX_BUILD_DESCENT_II)
  543.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  544. #endif
  545.         range_for (auto &&wp, vcwallptridx)
  546.         {
  547.                 auto &w = *wp;
  548.                 int     sidenum;
  549.  
  550.                 const auto i = static_cast<wallnum_t>(wp);
  551.                 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,
  552.                         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);
  553.  
  554. #if defined(DXX_BUILD_DESCENT_II)
  555.                 if (w.trigger >= Triggers.get_count())
  556.                         PHYSFSX_printf(my_file, "Wall %03d points to invalid trigger %d\n",i,w.trigger);
  557. #endif
  558.  
  559.                 auto segnum = w.segnum;
  560.                 sidenum = w.sidenum;
  561.  
  562.                 if (Segments[segnum].shared_segment::sides[sidenum].wall_num != wp)
  563.                         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<int16_t>(Segments[segnum].shared_segment::sides[sidenum].wall_num));
  564.         }
  565.  
  566.         wall_flags = {};
  567.  
  568.         range_for (const auto &&segp, vcsegptridx)
  569.         {
  570.                 range_for (const auto &&es, enumerate(segp->shared_segment::sides))
  571.                 {
  572.                         const auto sidep = &es.value;
  573.                         if (sidep->wall_num != wall_none)
  574.                         {
  575.                                 if (wall_flags[sidep->wall_num])
  576.                                         err_printf(my_file, "Error: Wall %hu appears in two or more segments, including segment %hu, side %" PRIuFAST32 ".", static_cast<int16_t>(sidep->wall_num), static_cast<segnum_t>(segp), es.idx);
  577.                                 else
  578.                                         wall_flags[sidep->wall_num] = 1;
  579.                         }
  580.                 }
  581.         }
  582.  
  583. }
  584. }
  585.  
  586. // ----------------------------------------------------------------------------
  587. namespace dsx {
  588. static void write_player_text(fvcobjptridx &vcobjptridx, PHYSFS_File *my_file)
  589. {
  590.         int     num_players=0;
  591.  
  592.         PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
  593.         PHYSFSX_printf(my_file, "Players:\n");
  594.         range_for (const auto &&objp, vcobjptridx)
  595.         {
  596.                 if (objp->type == OBJ_PLAYER)
  597.                 {
  598.                         num_players++;
  599.                         PHYSFSX_printf(my_file, "Player %2i is object #%3hu in segment #%3i.\n", get_player_id(objp), static_cast<uint16_t>(objp), objp->segnum);
  600.                 }
  601.         }
  602.  
  603. #if defined(DXX_BUILD_DESCENT_II)
  604.         if (num_players != MAX_PLAYERS)
  605.                 err_printf(my_file, "Error: %i player objects.  %i are required.", num_players, MAX_PLAYERS);
  606. #endif
  607.         if (num_players > MAX_MULTI_PLAYERS)
  608.                 err_printf(my_file, "Error: %i player objects.  %i are required.", num_players, MAX_PLAYERS);
  609. }
  610. }
  611.  
  612. // ----------------------------------------------------------------------------
  613. namespace dsx {
  614. static void write_trigger_text(PHYSFS_File *my_file)
  615. {
  616.         PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
  617.         PHYSFSX_printf(my_file, "Triggers:\n");
  618.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  619.         auto &vctrgptridx = Triggers.vcptridx;
  620.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  621.         auto &vcwallptr = Walls.vcptr;
  622.         range_for (auto &&t, vctrgptridx)
  623.         {
  624.                 const auto i = static_cast<trgnum_t>(t);
  625. #if defined(DXX_BUILD_DESCENT_I)
  626.                 PHYSFSX_printf(my_file, "Trigger %03i: flags=%04x, value=%08x, time=%8x, num_links=%i ", i, t->flags, static_cast<unsigned>(t->value), 0, t->num_links);
  627. #elif defined(DXX_BUILD_DESCENT_II)
  628.                 PHYSFSX_printf(my_file, "Trigger %03i: type=%02x flags=%04x, value=%08x, time=%8x, num_links=%i ", i,
  629.                         static_cast<uint8_t>(t->type), static_cast<uint8_t>(t->flags), t->value, 0, t->num_links);
  630. #endif
  631.  
  632.                 for (unsigned j = 0; j < t->num_links; ++j)
  633.                         PHYSFSX_printf(my_file, "[%03i:%i] ", t->seg[j], t->side[j]);
  634.  
  635.                 //      Find which wall this trigger is connected to.
  636.                 const auto &&we = vcwallptr.end();
  637.                 const auto &&wi = std::find_if(vcwallptr.begin(), we, [i](const wall *const p) { return p->trigger == i; });
  638.                 if (wi == we)
  639.                         err_printf(my_file, "Error: Trigger %i is not connected to any wall, so it can never be triggered.", i);
  640.                 else
  641.                 {
  642.                         const auto &&w = *wi;
  643.                         PHYSFSX_printf(my_file, "Attached to seg:side = %i:%i, wall %hi\n", w->segnum, w->sidenum, static_cast<int16_t>(vcsegptr(w->segnum)->shared_segment::sides[w->sidenum].wall_num));
  644.                 }
  645.         }
  646. }
  647. }
  648.  
  649. // ----------------------------------------------------------------------------
  650. void write_game_text_file(const char *filename)
  651. {
  652.         auto &Objects = LevelUniqueObjectState.Objects;
  653.         auto &vcobjptridx = Objects.vcptridx;
  654.         char    my_filename[128];
  655.         int     namelen;
  656.         Errors_in_mine = 0;
  657.  
  658.         namelen = strlen(filename);
  659.  
  660.         Assert (namelen > 4);
  661.  
  662.         Assert (filename[namelen-4] == '.');
  663.  
  664.         strcpy(my_filename, filename);
  665.         strcpy( &my_filename[namelen-4], ".txm");
  666.  
  667.         auto my_file = PHYSFSX_openWriteBuffered(my_filename);
  668.         if (!my_file)   {
  669.                 gr_palette_load(gr_palette);
  670.                 nm_messagebox( NULL, 1, "Ok", "ERROR: Unable to open %s\nErrno = %i", my_filename, errno);
  671.  
  672.                 return;
  673.         }
  674.  
  675.         dump_used_textures_level(my_file, 0, filename);
  676.         say_totals(vcobjptridx, my_file, filename);
  677.  
  678.         PHYSFSX_printf(my_file, "\nNumber of segments:   %4i\n", Highest_segment_index+1);
  679.         PHYSFSX_printf(my_file, "Number of objects:    %4i\n", Highest_object_index+1);
  680.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  681.         auto &vcwallptridx = Walls.vcptridx;
  682.         PHYSFSX_printf(my_file, "Number of walls:      %4i\n", Walls.get_count());
  683.         auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
  684.         PHYSFSX_printf(my_file, "Number of open doors: %4i\n", ActiveDoors.get_count());
  685.         {
  686.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  687.         PHYSFSX_printf(my_file, "Number of triggers:   %4i\n", Triggers.get_count());
  688.         }
  689.         PHYSFSX_printf(my_file, "Number of matcens:    %4i\n", LevelSharedRobotcenterState.Num_robot_centers);
  690.         PHYSFSX_printf(my_file, "\n");
  691.  
  692.         write_segment_text(vcsegptridx, my_file);
  693.  
  694.         write_fuelcen_text(my_file);
  695.  
  696.         write_matcen_text(my_file);
  697.  
  698.         write_player_text(vcobjptridx, my_file);
  699.  
  700.         write_wall_text(vcsegptridx, vcwallptridx, my_file);
  701.  
  702.         write_trigger_text(my_file);
  703.  
  704.         write_exit_text(vcsegptridx, vcwallptridx, my_file);
  705.  
  706.         //      ---------- Find control center segment ----------
  707.         write_control_center_text(vcsegptridx, my_file);
  708.  
  709.         //      ---------- Show keyed walls ----------
  710.         write_key_text(vcobjptridx, Segments, vcwallptridx, my_file);
  711. }
  712.  
  713. #if defined(DXX_BUILD_DESCENT_II)
  714. //      Adam: Change NUM_ADAM_LEVELS to the number of levels.
  715. #define NUM_ADAM_LEVELS 30
  716.  
  717. //      Adam: Stick the names here.
  718. constexpr char Adam_level_names[NUM_ADAM_LEVELS][13] = {
  719.         "D2LEVA-1.LVL",
  720.         "D2LEVA-2.LVL",
  721.         "D2LEVA-3.LVL",
  722.         "D2LEVA-4.LVL",
  723.         "D2LEVA-S.LVL",
  724.  
  725.         "D2LEVB-1.LVL",
  726.         "D2LEVB-2.LVL",
  727.         "D2LEVB-3.LVL",
  728.         "D2LEVB-4.LVL",
  729.         "D2LEVB-S.LVL",
  730.  
  731.         "D2LEVC-1.LVL",
  732.         "D2LEVC-2.LVL",
  733.         "D2LEVC-3.LVL",
  734.         "D2LEVC-4.LVL",
  735.         "D2LEVC-S.LVL",
  736.  
  737.         "D2LEVD-1.LVL",
  738.         "D2LEVD-2.LVL",
  739.         "D2LEVD-3.LVL",
  740.         "D2LEVD-4.LVL",
  741.         "D2LEVD-S.LVL",
  742.  
  743.         "D2LEVE-1.LVL",
  744.         "D2LEVE-2.LVL",
  745.         "D2LEVE-3.LVL",
  746.         "D2LEVE-4.LVL",
  747.         "D2LEVE-S.LVL",
  748.  
  749.         "D2LEVF-1.LVL",
  750.         "D2LEVF-2.LVL",
  751.         "D2LEVF-3.LVL",
  752.         "D2LEVF-4.LVL",
  753.         "D2LEVF-S.LVL",
  754. };
  755.  
  756. static int Ignore_tmap_num2_error;
  757. #endif
  758.  
  759. // ----------------------------------------------------------------------------
  760. namespace dsx {
  761. #if defined(DXX_BUILD_DESCENT_I)
  762. #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)
  763. #endif
  764. 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)
  765. {
  766. #if defined(DXX_BUILD_DESCENT_II)
  767.         auto &Objects = LevelUniqueObjectState.Objects;
  768.         auto &vcobjptr = Objects.vcptr;
  769. #endif
  770.         int     j;
  771.  
  772.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  773.         auto &WallAnims = GameSharedState.WallAnims;
  774. #if defined(DXX_BUILD_DESCENT_I)
  775.         tmap_buf = {};
  776.  
  777.         if (load_level_flag) {
  778.                 load_level(shareware_flag ? Shareware_level_names[level_num] : Registered_level_names[level_num]);
  779.         }
  780.  
  781.         range_for (const cscusegment segp, vcsegptr)
  782.         {
  783.                 range_for (const auto &&z, zip(segp.s.sides, segp.u.sides))
  784.                 {
  785.                         auto &sside = std::get<0>(z);
  786.                         if (sside.wall_num != wall_none)
  787.                         {
  788.                                 const auto clip_num = Walls.vcptr(sside.wall_num)->clip_num;
  789.                                 if (clip_num != -1) {
  790.  
  791.                                         const auto num_frames = WallAnims[clip_num].num_frames;
  792.  
  793.                                         wall_buf[clip_num] = 1;
  794.  
  795.                                         for (j=0; j<num_frames; j++) {
  796.                                                 int     tmap_num;
  797.  
  798.                                                 tmap_num = WallAnims[clip_num].frames[j];
  799.                                                 tmap_buf[tmap_num]++;
  800.                                                 if (level_tmap_buf[tmap_num] == -1)
  801.                                                         level_tmap_buf[tmap_num] = level_num + (!shareware_flag) * NUM_SHAREWARE_LEVELS;
  802.                                         }
  803.                                 }
  804.                         }
  805.  
  806.                         auto &uside = std::get<1>(z);
  807.                         if (uside.tmap_num >= 0)
  808.                          {
  809.                                 if (uside.tmap_num < max_tmap)
  810.                                  {
  811.                                         tmap_buf[uside.tmap_num]++;
  812.                                         if (level_tmap_buf[uside.tmap_num] == -1)
  813.                                                 level_tmap_buf[uside.tmap_num] = level_num + (!shareware_flag) * NUM_SHAREWARE_LEVELS;
  814.                                  }
  815.                                 else
  816.                                  {
  817.                                         Int3(); //      Error, bogus texture map.  Should not be greater than max_tmap.
  818.                                  }
  819.                          }
  820.  
  821.                         if (const auto tmap_num2 = uside.tmap_num2 & 0x3fff)
  822.                          {
  823.                                 if (tmap_num2 < max_tmap) {
  824.                                         ++tmap_buf[tmap_num2];
  825.                                         if (level_tmap_buf[tmap_num2] == -1)
  826.                                                 level_tmap_buf[tmap_num2] = level_num + (!shareware_flag) * NUM_SHAREWARE_LEVELS;
  827.                                 } else
  828.                                         Int3(); //      Error, bogus texture map.  Should not be greater than max_tmap.
  829.                          }
  830.                  }
  831.          }
  832. #elif defined(DXX_BUILD_DESCENT_II)
  833.         (void)max_tmap;
  834.         (void)shareware_flag;
  835.  
  836.         tmap_buf = {};
  837.  
  838.         if (load_level_flag) {
  839.                 load_level(LevelSharedDestructibleLightState, Adam_level_names[level_num]);
  840.         }
  841.  
  842.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  843.         //      Process robots.
  844.         range_for (const auto &&objp, vcobjptr)
  845.         {
  846.                 if (objp->render_type == RT_POLYOBJ) {
  847.                         polymodel *po = &Polygon_models[objp->rtype.pobj_info.model_num];
  848.  
  849.                         for (unsigned i = 0; i < po->n_textures; ++i)
  850.                         {
  851.                                 unsigned tli = ObjBitmaps[ObjBitmapPtrs[po->first_texture+i]].index;
  852.  
  853.                                 if (tli < tmap_buf.size())
  854.                                 {
  855.                                         tmap_buf[tli]++;
  856.                                         if (level_tmap_buf[tli] == -1)
  857.                                                 level_tmap_buf[tli] = level_num;
  858.                                 } else
  859.                                         Int3(); //      Hmm, It seems this texture is bogus!
  860.                         }
  861.  
  862.                 }
  863.         }
  864.  
  865.  
  866.         Ignore_tmap_num2_error = 0;
  867.  
  868.         //      Process walls and segment sides.
  869.         range_for (const csmusegment segp, vmsegptr)
  870.         {
  871.                 range_for (const auto &&z, zip(segp.s.sides, segp.u.sides, segp.s.children))
  872.                 {
  873.                         auto &sside = std::get<0>(z);
  874.                         auto &uside = std::get<1>(z);
  875.                         const auto child = std::get<2>(z);
  876.                         if (sside.wall_num != wall_none) {
  877.                                 const auto clip_num = Walls.vcptr(sside.wall_num)->clip_num;
  878.                                 if (clip_num != -1) {
  879.  
  880.                                         // -- int num_frames = WallAnims[clip_num].num_frames;
  881.  
  882.                                         wall_buf[clip_num] = 1;
  883.  
  884.                                         for (j=0; j<1; j++) {   //      Used to do through num_frames, but don't really want all the door01#3 stuff.
  885.                                                 unsigned tmap_num = Textures[WallAnims[clip_num].frames[j]].index;
  886.                                                 Assert(tmap_num < tmap_buf.size());
  887.                                                 tmap_buf[tmap_num]++;
  888.                                                 if (level_tmap_buf[tmap_num] == -1)
  889.                                                         level_tmap_buf[tmap_num] = level_num;
  890.                                         }
  891.                                 }
  892.                         } else if (child == segment_none) {
  893.  
  894.                                 if (uside.tmap_num >= 0)
  895.                                 {
  896.                                         if (uside.tmap_num < Textures.size()) {
  897.                                                 const auto ti = Textures[uside.tmap_num].index;
  898.                                                 assert(ti < tmap_buf.size());
  899.                                                 ++tmap_buf[ti];
  900.                                                 if (level_tmap_buf[ti] == -1)
  901.                                                         level_tmap_buf[ti] = level_num;
  902.                                         } else
  903.                                                 Int3(); //      Error, bogus texture map.  Should not be greater than max_tmap.
  904.                                 }
  905.  
  906.                                 if (const auto masked_tmap_num2 = (uside.tmap_num2 & 0x3fff))
  907.                                 {
  908.                                         if (masked_tmap_num2 < Textures.size())
  909.                                         {
  910.                                                 const auto ti = Textures[masked_tmap_num2].index;
  911.                                                 assert(ti < tmap_buf.size());
  912.                                                 ++tmap_buf[ti];
  913.                                                 if (level_tmap_buf[ti] == -1)
  914.                                                         level_tmap_buf[ti] = level_num;
  915.                                         } else {
  916.                                                 if (!Ignore_tmap_num2_error)
  917.                                                         Int3(); //      Error, bogus texture map.  Should not be greater than max_tmap.
  918.                                                 Ignore_tmap_num2_error = 1;
  919.                                                 uside.tmap_num2 = 0;
  920.                                         }
  921.                                 }
  922.                         }
  923.                 }
  924.         }
  925. #endif
  926. }
  927. }
  928.  
  929. // ----------------------------------------------------------------------------
  930. template <std::size_t N>
  931. static void merge_buffers(std::array<int, N> &dest, const std::array<int, N> &src)
  932. {
  933.         std::transform(dest.begin(), dest.end(), src.begin(), dest.begin(), std::plus<int>());
  934. }
  935.  
  936. // ----------------------------------------------------------------------------
  937. namespace dsx {
  938. static void say_used_tmaps(PHYSFS_File *const my_file, const perm_tmap_buffer_type &tb)
  939. {
  940.         int     i;
  941. #if defined(DXX_BUILD_DESCENT_I)
  942.         int     count = 0;
  943.  
  944.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  945.         const auto Num_tmaps = LevelUniqueTmapInfoState.Num_tmaps;
  946.         for (i=0; i<Num_tmaps; i++)
  947.                 if (tb[i]) {
  948.                         PHYSFSX_printf(my_file, "[%3i %8s (%4i)] ", i, static_cast<const char *>(TmapInfo[i].filename), tb[i]);
  949.                         if (count++ >= 4) {
  950.                                 PHYSFSX_printf(my_file, "\n");
  951.                                 count = 0;
  952.                         }
  953.                 }
  954. #elif defined(DXX_BUILD_DESCENT_II)
  955.         for (i = 0; i < tb.size(); ++i)
  956.                 if (tb[i]) {
  957.                         PHYSFSX_printf(my_file, "[%3i %8s (%4i)]\n", i, AllBitmaps[i].name.data(), tb[i]);
  958.                 }
  959. #endif
  960. }
  961. }
  962.  
  963. #if defined(DXX_BUILD_DESCENT_I)
  964. //      -----------------------------------------------------------------------------
  965. static void say_used_once_tmaps(PHYSFS_File *const my_file, const perm_tmap_buffer_type &tb, const level_tmap_buffer_type &tb_lnum)
  966. {
  967.         int     i;
  968.         const char      *level_name;
  969.  
  970.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  971.         const auto Num_tmaps = LevelUniqueTmapInfoState.Num_tmaps;
  972.         for (i=0; i<Num_tmaps; i++)
  973.                 if (tb[i] == 1) {
  974.                         int     level_num = tb_lnum[i];
  975.                         if (level_num >= NUM_SHAREWARE_LEVELS) {
  976.                                 Assert((level_num - NUM_SHAREWARE_LEVELS >= 0) && (level_num - NUM_SHAREWARE_LEVELS < NUM_REGISTERED_LEVELS));
  977.                                 level_name = Registered_level_names[level_num - NUM_SHAREWARE_LEVELS];
  978.                         } else {
  979.                                 Assert((level_num >= 0) && (level_num < NUM_SHAREWARE_LEVELS));
  980.                                 level_name = Shareware_level_names[level_num];
  981.                         }
  982.  
  983.                         PHYSFSX_printf(my_file, "Texture %3i %8s used only once on level %s\n", i, static_cast<const char *>(TmapInfo[i].filename), level_name);
  984.                 }
  985. }
  986. #endif
  987.  
  988. // ----------------------------------------------------------------------------
  989. namespace dsx {
  990. static void say_unused_tmaps(PHYSFS_File *my_file, perm_tmap_buffer_type &tb)
  991. {
  992.         int     i;
  993.         int     count = 0;
  994.  
  995. #if defined(DXX_BUILD_DESCENT_I)
  996.         const unsigned bound = LevelUniqueTmapInfoState.Num_tmaps;
  997.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  998. #elif defined(DXX_BUILD_DESCENT_II)
  999.         const unsigned bound = MAX_BITMAP_FILES;
  1000. #endif
  1001.         for (i=0; i < bound; i++)
  1002.                 if (!tb[i]) {
  1003.                         if (GameBitmaps[Textures[i].index].bm_data == bogus_data.data())
  1004.                                 PHYSFSX_printf(my_file, "U");
  1005.                         else
  1006.                                 PHYSFSX_printf(my_file, " ");
  1007.  
  1008. #if defined(DXX_BUILD_DESCENT_I)
  1009.                         PHYSFSX_printf(my_file, "[%3i %8s] ", i, static_cast<const char *>(TmapInfo[i].filename));
  1010. #elif defined(DXX_BUILD_DESCENT_II)
  1011.                         PHYSFSX_printf(my_file, "[%3i %8s] ", i, AllBitmaps[i].name.data());
  1012. #endif
  1013.                         if (count++ >= 4) {
  1014.                                 PHYSFSX_printf(my_file, "\n");
  1015.                                 count = 0;
  1016.                         }
  1017.                 }
  1018. }
  1019. }
  1020.  
  1021. #if defined(DXX_BUILD_DESCENT_I)
  1022. // ----------------------------------------------------------------------------
  1023. static void say_unused_walls(PHYSFS_File *my_file, const wall_buffer_type &tb)
  1024. {
  1025.         int     i;
  1026.         for (i=0; i<Num_wall_anims; i++)
  1027.                 if (!tb[i])
  1028.                         PHYSFSX_printf(my_file, "Wall %3i is unused.\n", i);
  1029. }
  1030. #endif
  1031.  
  1032. static void say_totals(fvcobjptridx &vcobjptridx, PHYSFS_File *my_file, const char *level_name)
  1033. {
  1034.         auto &Objects = LevelUniqueObjectState.Objects;
  1035.         int     total_robots = 0;
  1036.         int     objects_processed = 0;
  1037.  
  1038.         PHYSFSX_printf(my_file, "\nLevel %s\n", level_name);
  1039.         std::bitset<MAX_OBJECTS> used_objects;
  1040.         while (objects_processed < Highest_object_index+1) {
  1041.                 int     objtype, objid, objcount, min_obj_val;
  1042.  
  1043.                 //      Find new min objnum.
  1044.                 min_obj_val = 0x7fff0000;
  1045.                 const object_base *min_objp = nullptr;
  1046.  
  1047.                 range_for (const auto &&objp, vcobjptridx)
  1048.                 {
  1049.                         if (!used_objects[objp] && objp->type != OBJ_NONE)
  1050.                         {
  1051.                                 const auto cur_obj_val = (objp->type << 10) + objp->id;
  1052.                                 if (cur_obj_val < min_obj_val) {
  1053.                                         min_objp = &*objp;
  1054.                                         min_obj_val = cur_obj_val;
  1055.                                 }
  1056.                         }
  1057.                 }
  1058.                 if (!min_objp || min_objp->type == OBJ_NONE)
  1059.                         break;
  1060.  
  1061.                 objcount = 0;
  1062.  
  1063.                 objtype = min_objp->type;
  1064.                 objid = min_objp->id;
  1065.  
  1066.                 range_for (const auto &&objp, vcobjptridx)
  1067.                 {
  1068.                         if (auto &&uo = used_objects[objp])
  1069.                         {
  1070.                         }
  1071.                         else
  1072.                         {
  1073.                                 if ((objp->type == objtype && objp->id == objid) ||
  1074.                                                 (objp->type == objtype && objtype == OBJ_PLAYER) ||
  1075.                                                 (objp->type == objtype && objtype == OBJ_COOP) ||
  1076.                                                 (objp->type == objtype && objtype == OBJ_HOSTAGE)) {
  1077.                                         if (objp->type == OBJ_ROBOT)
  1078.                                                 total_robots++;
  1079.                                         uo = true;
  1080.                                         objcount++;
  1081.                                         objects_processed++;
  1082.                                 }
  1083.                         }
  1084.                 }
  1085.  
  1086.                 if (objcount) {
  1087.                         PHYSFSX_printf(my_file, "Object: %8s %8s %3i\n", object_types(*min_objp), object_ids(*min_objp), objcount);
  1088.                 }
  1089.         }
  1090.  
  1091.         PHYSFSX_printf(my_file, "Total robots = %3i\n", total_robots);
  1092. }
  1093.  
  1094. #if defined(DXX_BUILD_DESCENT_II)
  1095. int     First_dump_level = 0;
  1096. int     Last_dump_level = NUM_ADAM_LEVELS-1;
  1097. #endif
  1098.  
  1099. // ----------------------------------------------------------------------------
  1100. namespace dsx {
  1101. static void say_totals_all(void)
  1102. {
  1103.         auto &Objects = LevelUniqueObjectState.Objects;
  1104.         auto &vcobjptridx = Objects.vcptridx;
  1105.         int     i;
  1106.         auto my_file = PHYSFSX_openWriteBuffered("levels.all");
  1107.         if (!my_file)   {
  1108.                 gr_palette_load(gr_palette);
  1109.                 nm_messagebox( NULL, 1, "Ok", "ERROR: Unable to open levels.all\nErrno=%i", errno );
  1110.  
  1111.                 return;
  1112.         }
  1113.  
  1114. #if defined(DXX_BUILD_DESCENT_I)
  1115.         for (i=0; i<NUM_SHAREWARE_LEVELS; i++) {
  1116.                 load_level(Shareware_level_names[i]);
  1117.                 say_totals(vcobjptridx, my_file, Shareware_level_names[i]);
  1118.         }
  1119.  
  1120.         for (i=0; i<NUM_REGISTERED_LEVELS; i++) {
  1121.                 load_level(Registered_level_names[i]);
  1122.                 say_totals(vcobjptridx, my_file, Registered_level_names[i]);
  1123.         }
  1124. #elif defined(DXX_BUILD_DESCENT_II)
  1125.         for (i=First_dump_level; i<=Last_dump_level; i++) {
  1126.                 load_level(LevelSharedSegmentState.DestructibleLights, Adam_level_names[i]);
  1127.                 say_totals(vcobjptridx, my_file, Adam_level_names[i]);
  1128.         }
  1129. #endif
  1130. }
  1131. }
  1132.  
  1133. static void dump_used_textures_level(PHYSFS_File *my_file, int level_num, const char *const Gamesave_current_filename)
  1134. {
  1135.         perm_tmap_buffer_type temp_tmap_buf;
  1136.         level_tmap_buffer_type level_tmap_buf;
  1137.  
  1138.         level_tmap_buf.fill(-1);
  1139.  
  1140.         wall_buffer_type temp_wall_buf;
  1141.         determine_used_textures_level(LevelSharedSegmentState.DestructibleLights, 0, 1, level_num, temp_tmap_buf, temp_wall_buf, level_tmap_buf, level_tmap_buf.size());
  1142.         PHYSFSX_printf(my_file, "\nTextures used in [%s]\n", Gamesave_current_filename);
  1143.         say_used_tmaps(my_file, temp_tmap_buf);
  1144. }
  1145.  
  1146. // ----------------------------------------------------------------------------
  1147. namespace dsx {
  1148. void dump_used_textures_all(void)
  1149. {
  1150.         int     i;
  1151.  
  1152. say_totals_all();
  1153.  
  1154.         auto my_file = PHYSFSX_openWriteBuffered("textures.dmp");
  1155.  
  1156.         if (!my_file)   {
  1157.                 gr_palette_load(gr_palette);
  1158.                 nm_messagebox( NULL, 1, "Ok", "ERROR: Can't open textures.dmp\nErrno=%i", errno);
  1159.  
  1160.                 return;
  1161.         }
  1162.  
  1163.         perm_tmap_buffer_type perm_tmap_buf{};
  1164.         level_tmap_buffer_type level_tmap_buf;
  1165.         level_tmap_buf.fill(-1);
  1166.  
  1167.         perm_tmap_buffer_type temp_tmap_buf;
  1168. #if defined(DXX_BUILD_DESCENT_I)
  1169.         wall_buffer_type perm_wall_buf{};
  1170.  
  1171.         for (i=0; i<NUM_SHAREWARE_LEVELS; i++) {
  1172.                 wall_buffer_type temp_wall_buf;
  1173.                 determine_used_textures_level(LevelSharedDestructibleLightState, 1, 1, i, temp_tmap_buf, temp_wall_buf, level_tmap_buf, level_tmap_buf.size());
  1174.                 PHYSFSX_printf(my_file, "\nTextures used in [%s]\n", Shareware_level_names[i]);
  1175.                 say_used_tmaps(my_file, temp_tmap_buf);
  1176.                 merge_buffers(perm_tmap_buf, temp_tmap_buf);
  1177.                 merge_buffers(perm_wall_buf, temp_wall_buf);
  1178.         }
  1179.  
  1180.         PHYSFSX_printf(my_file, "\n\nUsed textures in all shareware mines:\n");
  1181.         say_used_tmaps(my_file, perm_tmap_buf);
  1182.  
  1183.         PHYSFSX_printf(my_file, "\nUnused textures in all shareware mines:\n");
  1184.         say_unused_tmaps(my_file, perm_tmap_buf);
  1185.  
  1186.         PHYSFSX_printf(my_file, "\nTextures used exactly once in all shareware mines:\n");
  1187.         say_used_once_tmaps(my_file, perm_tmap_buf, level_tmap_buf);
  1188.  
  1189.         PHYSFSX_printf(my_file, "\nWall anims (eg, doors) unused in all shareware mines:\n");
  1190.         say_unused_walls(my_file, perm_wall_buf);
  1191.  
  1192.         for (i=0; i<NUM_REGISTERED_LEVELS; i++)
  1193. #elif defined(DXX_BUILD_DESCENT_II)
  1194.         for (i=First_dump_level; i<=Last_dump_level; i++)
  1195. #endif
  1196.         {
  1197.                 wall_buffer_type temp_wall_buf;
  1198.                 determine_used_textures_level(LevelSharedSegmentState.DestructibleLights, 1, 0, i, temp_tmap_buf, temp_wall_buf, level_tmap_buf, level_tmap_buf.size());
  1199. #if defined(DXX_BUILD_DESCENT_I)
  1200.                 PHYSFSX_printf(my_file, "\nTextures used in [%s]\n", Registered_level_names[i]);
  1201. #elif defined(DXX_BUILD_DESCENT_II)
  1202.                 PHYSFSX_printf(my_file, "\nTextures used in [%s]\n", Adam_level_names[i]);
  1203. #endif
  1204.                 say_used_tmaps(my_file, temp_tmap_buf);
  1205.                 merge_buffers(perm_tmap_buf, temp_tmap_buf);
  1206.         }
  1207.  
  1208.         PHYSFSX_printf(my_file, "\n\nUsed textures in all (including registered) mines:\n");
  1209.         say_used_tmaps(my_file, perm_tmap_buf);
  1210.  
  1211.         PHYSFSX_printf(my_file, "\nUnused textures in all (including registered) mines:\n");
  1212.         say_unused_tmaps(my_file, perm_tmap_buf);
  1213. }
  1214. }
  1215.  
  1216. #endif
  1217.