dxx-rebirth/similar/main/dumpmine.cpp
Kp 4658abc87e Remove Adam_level_names[]
These refer to the Miner levels, which were removed in the preceding
commit since support for them has been marked as broken for an extended
period, and no one has reported an issue.
2020-12-20 20:39:07 +00:00

1155 lines
36 KiB
C++

/*
* 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 <bitset>
#include <stdio.h>
#include <cinttypes>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#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 "text.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 "d_enumerate.h"
#include "d_levelstate.h"
#include "d_range.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<int, MAX_TEXTURES>;
using level_tmap_buffer_type = std::array<int8_t, MAX_TEXTURES>;
using wall_buffer_type = std::array<int, MAX_WALL_ANIMS>;
#elif defined(DXX_BUILD_DESCENT_II)
using perm_tmap_buffer_type = std::array<int, MAX_BITMAP_FILES>;
using level_tmap_buffer_type = std::array<int8_t, MAX_BITMAP_FILES>;
using wall_buffer_type = std::array<int, MAX_BITMAP_FILES>;
#endif
}
}
namespace dcx {
const std::array<char[10], 7> Wall_names{{
"NORMAL ",
"BLASTABLE",
"DOOR ",
"ILLUSION ",
"OPEN ",
"CLOSED ",
"EXTERNAL "
}};
}
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 {
const std::array<char[9], MAX_OBJECT_TYPES> 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 <size_t len>
static void err_puts_literal(PHYSFS_File *f, const char (&str)[len]) __attribute_nonnull();
template <size_t len>
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 <size_t len>
static void warning_puts_literal(PHYSFS_File *f, const char (&str)[len]) __attribute_nonnull();
template <size_t len>
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 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<wallnum_t>(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 (const auto &&[child_segnum, sidenum] : enumerate(segp->shared_segment::children))
{
if (child_segnum == segment_exit)
{
PHYSFSX_printf(my_file, "Segment %3hu, side %" PRIuFAST32 " is an exit door.\n", segp.get_unchecked_index(), sidenum);
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 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<wallnum_t>(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, 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<objnum_t>(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<objnum_t>(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_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<uint16_t>(segp));
count2 = 0;
range_for (const object &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)
{
auto &Station = LevelUniqueFuelcenterState.Station;
int i;
PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
PHYSFSX_printf(my_file, "Fuel Center stuff: (Note: This means fuel, repair, materialize, control centers!)\n");
const auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters;
for (i=0; i<Num_fuelcenters; i++) {
PHYSFSX_printf(my_file, "Fuelcenter %i: Type=%i (%s), segment = %3i\n", i, Station[i].Type, Special_names[Station[i].Type], Station[i].segnum);
if (Segments[Station[i].segnum].special != Station[i].Type)
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);
}
}
// ----------------------------------------------------------------------------
static void write_segment_text(fvcsegptridx &vcsegptridx, PHYSFS_File *my_file)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vcobjptridx = Objects.vcptridx;
PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n");
PHYSFSX_printf(my_file, "Segment stuff:\n");
range_for (const auto &&segp, vcsegptridx)
{
PHYSFSX_printf(my_file, "Segment %4hu:", static_cast<uint16_t>(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<uint16_t>(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 &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 (i=0; i<Num_robot_centers; i++) {
int trigger_count=0, fuelcen_num;
PHYSFSX_printf(my_file, "FuelCenter[%02i].Segment = %04i ", i, Station[i].segnum);
PHYSFSX_printf(my_file, "Segment[%04i].matcen_num = %02i ", Station[i].segnum, Segments[Station[i].segnum].matcen_num);
fuelcen_num = RobotCenters[i].fuelcen_num;
if (Station[fuelcen_num].Type != SEGMENT_IS_ROBOTMAKER)
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]);
auto segnum = Station[fuelcen_num].segnum;
// 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 ", 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)
{
std::array<int8_t, MAX_WALLS> 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<wallnum_t>(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, static_cast<unsigned>(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<int16_t>(Segments[segnum].shared_segment::sides[sidenum].wall_num));
}
wall_flags = {};
range_for (const auto &&segp, vcsegptridx)
{
range_for (const auto &&es, enumerate(segp->shared_segment::sides))
{
const auto sidep = &es.value;
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 %" PRIuFAST32 ".", static_cast<int16_t>(sidep->wall_num), static_cast<segnum_t>(segp), es.idx);
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<uint16_t>(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<trgnum_t>(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<unsigned>(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,
static_cast<uint8_t>(t->type), static_cast<uint8_t>(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<int16_t>(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 = 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\nErrno = %i", my_filename, errno);
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", 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", LevelSharedRobotcenterState.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)
static int Ignore_tmap_num2_error;
#endif
// ----------------------------------------------------------------------------
namespace dsx {
#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<num_frames; j++) {
int tmap_num;
tmap_num = WallAnims[clip_num].frames[j];
tmap_buf[tmap_num]++;
if (level_tmap_buf[tmap_num] == -1)
level_tmap_buf[tmap_num] = level_num + (!shareware_flag) * NUM_SHAREWARE_LEVELS;
}
}
}
auto &uside = std::get<1>(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
}
}
// ----------------------------------------------------------------------------
template <std::size_t N>
static void merge_buffers(std::array<int, N> &dest, const std::array<int, N> &src)
{
std::transform(dest.begin(), dest.end(), src.begin(), dest.begin(), std::plus<int>());
}
// ----------------------------------------------------------------------------
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;
const auto Num_tmaps = LevelUniqueTmapInfoState.Num_tmaps;
for (i=0; i<Num_tmaps; i++)
if (tb[i]) {
PHYSFSX_printf(my_file, "[%3i %8s (%4i)] ", i, static_cast<const char *>(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;
const auto Num_tmaps = LevelUniqueTmapInfoState.Num_tmaps;
for (i=0; i<Num_tmaps; i++)
if (tb[i] == 1) {
int level_num = tb_lnum[i];
if (level_num >= 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<const char *>(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 = LevelUniqueTmapInfoState.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<const char *>(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<Num_wall_anims; i++)
if (!tb[i])
PHYSFSX_printf(my_file, "Wall %3i is unused.\n", i);
}
#endif
static void say_totals(fvcobjptridx &vcobjptridx, PHYSFS_File *my_file, const char *level_name)
{
auto &Objects = LevelUniqueObjectState.Objects;
int total_robots = 0;
int objects_processed = 0;
PHYSFSX_printf(my_file, "\nLevel %s\n", level_name);
std::bitset<MAX_OBJECTS> 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;
#endif
// ----------------------------------------------------------------------------
namespace dsx {
static void say_totals_all(void)
{
auto my_file = 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\nErrno=%i", errno );
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
}
}
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 = 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\nErrno=%i", errno);
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_printf(my_file, "\n\nUsed textures in all shareware mines:\n");
say_used_tmaps(my_file, perm_tmap_buf);
PHYSFSX_printf(my_file, "\nUnused textures in all shareware mines:\n");
say_unused_tmaps(my_file, perm_tmap_buf);
PHYSFSX_printf(my_file, "\nTextures used exactly once in all shareware mines:\n");
say_used_once_tmaps(my_file, perm_tmap_buf, level_tmap_buf);
PHYSFSX_printf(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_printf(my_file, "\n\nUsed textures in all (including registered) mines:\n");
say_used_tmaps(my_file, perm_tmap_buf);
PHYSFSX_printf(my_file, "\nUnused textures in all (including registered) mines:\n");
say_unused_tmaps(my_file, perm_tmap_buf);
}
}
#endif