/* * Portions of this file are copyright Rebirth contributors and licensed as * described in COPYING.txt. * Portions of this file are copyright Parallax Software and licensed * according to the Parallax license below. * See COPYING.txt for license details. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. */ /* * * Functions to dump text description of mine. * An editor-only function, called at mine load time. * To be read by a human to verify the correctness and completeness of a mine. * */ #include #include #include #include #include #include "pstypes.h" #include "console.h" #include "physfsx.h" #include "key.h" #include "gr.h" #include "palette.h" #include "fmtcheck.h" #include "inferno.h" #ifdef EDITOR #include "editor/editor.h" #endif #include "dxxerror.h" #include "object.h" #include "wall.h" #include "gamemine.h" #include "gameseg.h" #include "robot.h" #include "player.h" #include "newmenu.h" #include "textures.h" #include "bm.h" #include "menu.h" #include "switch.h" #include "fuelcen.h" #include "powerup.h" #include "gameseq.h" #include "polyobj.h" #include "gamesave.h" #include "piggy.h" #include "compiler-range_for.h" #include "segiter.h" #ifdef EDITOR static void dump_used_textures_level(PHYSFS_file *my_file, int level_num); static void say_totals(PHYSFS_file *my_file, const char *level_name); // ---------------------------------------------------------------------------- static const char *object_types(int objnum) { int type = Objects[objnum].type; Assert((type == OBJ_NONE) || ((type >= 0) && (type < MAX_OBJECT_TYPES))); return Object_type_names[type]; } // ---------------------------------------------------------------------------- static char *object_ids(int objnum) { int type = Objects[objnum].type; switch (type) { case OBJ_ROBOT: return Robot_names[get_robot_id(&Objects[objnum])]; break; case OBJ_POWERUP: return Powerup_names[get_powerup_id(&Objects[objnum])]; break; } return NULL; } static void err_puts(PHYSFS_file *f, const char *str, size_t len) __attribute_nonnull(); static void err_puts(PHYSFS_file *f, const char *str, size_t len) #define err_puts(A1,S,...) (err_puts(A1,S, _dxx_call_puts_parameter2(1, ## __VA_ARGS__, strlen(S)))) { con_puts(CON_CRITICAL, str, len); PHYSFSX_puts(f, str); Errors_in_mine++; } template static void err_puts_literal(PHYSFS_file *f, const char (&str)[len]) __attribute_nonnull(); template static void err_puts_literal(PHYSFS_file *f, const char (&str)[len]) { err_puts(f, str, len); } static void err_printf(PHYSFS_file *my_file, const char * format, ... ) __attribute_format_printf(2, 3); static void err_printf(PHYSFS_file *my_file, const char * format, ... ) #define err_printf(A1,F,...) dxx_call_printf_checked(err_printf,err_puts_literal,(A1),(F),##__VA_ARGS__) { va_list args; char message[256]; va_start(args, format ); size_t len = vsnprintf(message,sizeof(message),format,args); va_end(args); err_puts(my_file, message, len); } static void warning_puts(PHYSFS_file *f, const char *str, size_t len) __attribute_nonnull(); static void warning_puts(PHYSFS_file *f, const char *str, size_t len) #define warning_puts(A1,S,...) (warning_puts(A1,S, _dxx_call_puts_parameter2(1, ## __VA_ARGS__, strlen(S)))) { con_puts(CON_URGENT, str, len); PHYSFSX_puts(f, str); } template static void warning_puts_literal(PHYSFS_file *f, const char (&str)[len]) __attribute_nonnull(); template static void warning_puts_literal(PHYSFS_file *f, const char (&str)[len]) { warning_puts(f, str, len); } static void warning_printf(PHYSFS_file *my_file, const char * format, ... ) __attribute_format_printf(2, 3); static void warning_printf(PHYSFS_file *my_file, const char * format, ... ) #define warning_printf(A1,F,...) dxx_call_printf_checked(warning_printf,warning_puts_literal,(A1),(F),##__VA_ARGS__) { va_list args; char message[256]; va_start(args, format ); vsnprintf(message,sizeof(message),format,args); va_end(args); warning_puts(my_file, message); } // ---------------------------------------------------------------------------- static void write_exit_text(PHYSFS_file *my_file) { int i, j, count; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Exit stuff\n"); // ---------- Find exit triggers ---------- count=0; for (i=0; i 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; for (i=0; i<=Highest_segment_index; i++) for (j=0; j 1) warning_printf(my_file, "Warning: %i doors are keyed to the blue key.", blue_count); if (red_count > 1) warning_printf(my_file, "Warning: %i doors are keyed to the red key.", red_count); if (gold_count > 1) warning_printf(my_file, "Warning: %i doors are keyed to the gold key.", gold_count); red_count2 = 0; blue_count2 = 0; gold_count2 = 0; for (i=0; i<=Highest_object_index; i++) { if (Objects[i].type == OBJ_POWERUP) if (get_powerup_id(&Objects[i]) == POW_KEY_BLUE) { PHYSFSX_printf(my_file, "The BLUE key is object %i in segment %i\n", i, Objects[i].segnum); blue_count2++; } if (Objects[i].type == OBJ_POWERUP) if (get_powerup_id(&Objects[i]) == POW_KEY_RED) { PHYSFSX_printf(my_file, "The RED key is object %i in segment %i\n", i, Objects[i].segnum); red_count2++; } if (Objects[i].type == OBJ_POWERUP) if (get_powerup_id(&Objects[i]) == POW_KEY_GOLD) { PHYSFSX_printf(my_file, "The GOLD key is object %i in segment %i\n", i, Objects[i].segnum); gold_count2++; } if (Objects[i].contains_count) { if (Objects[i].contains_type == OBJ_POWERUP) { switch (Objects[i].contains_id) { case POW_KEY_BLUE: PHYSFSX_printf(my_file, "The BLUE key is contained in object %i (a %s %s) in segment %i\n", i, Object_type_names[Objects[i].type], Robot_names[get_robot_id(&Objects[i])], Objects[i].segnum); blue_count2 += Objects[i].contains_count; break; case POW_KEY_GOLD: PHYSFSX_printf(my_file, "The GOLD key is contained in object %i (a %s %s) in segment %i\n", i, Object_type_names[Objects[i].type], Robot_names[get_robot_id(&Objects[i])], Objects[i].segnum); gold_count2 += Objects[i].contains_count; break; case POW_KEY_RED: PHYSFSX_printf(my_file, "The RED key is contained in object %i (a %s %s) in segment %i\n", i, Object_type_names[Objects[i].type], Robot_names[get_robot_id(&Objects[i])], Objects[i].segnum); red_count2 += Objects[i].contains_count; break; default: break; } } } } if (blue_count) if (blue_count2 == 0) err_printf(my_file, "Error: There is a door keyed to the blue key, but no blue key!"); if (red_count) if (red_count2 == 0) err_printf(my_file, "Error: There is a door keyed to the red key, but no red key!"); if (gold_count) if (gold_count2 == 0) err_printf(my_file, "Error: There is a door keyed to the gold key, but no gold key!"); if (blue_count2 > 1) err_printf(my_file, "Error: There are %i blue keys!", blue_count2); if (red_count2 > 1) err_printf(my_file, "Error: There are %i red keys!", red_count2); if (gold_count2 > 1) err_printf(my_file, "Error: There are %i gold keys!", gold_count2); } // ---------------------------------------------------------------------------- static void write_control_center_text(PHYSFS_file *my_file) { int i, count, count2; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Control Center stuff:\n"); count = 0; for (i=0; i<=Highest_segment_index; i++) if (Segments[i].special == SEGMENT_IS_CONTROLCEN) { count++; PHYSFSX_printf(my_file, "Segment %3i is a control center.\n", i); count2 = 0; range_for (auto objp, objects_in(Segments[i])) { if (objp->type == OBJ_CNTRLCEN) count2++; } if (count2 == 0) PHYSFSX_printf(my_file, "No control center object in control center segment.\n"); else if (count2 != 1) PHYSFSX_printf(my_file, "%i control center objects in control center segment.\n", count2); } if (count == 0) err_printf(my_file, "Error: No control center in this mine."); else if (count != 1) err_printf(my_file, "Error: More than one control center in this mine."); } // ---------------------------------------------------------------------------- static void write_fuelcen_text(PHYSFS_file *my_file) { int i; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Fuel Center stuff: (Note: This means fuel, repair, materialize, control centers!)\n"); for (i=0; i 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, j, k; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Materialization centers:\n"); for (i=0; i> 16, Walls[i].trigger, Walls[i].clip_num, Walls[i].keys, Walls[i].state); #if defined(DXX_BUILD_DESCENT_II) if (Walls[i].trigger >= Num_triggers) PHYSFSX_printf(my_file, "Wall %03d points to invalid trigger %d\n",i,Walls[i].trigger); #endif segnum_t segnum = Walls[i].segnum; sidenum = Walls[i].sidenum; if (Segments[segnum].sides[sidenum].wall_num != i) err_printf(my_file, "Error: Wall %i points at segment %i, side %i, but that segment doesn't point back (it's wall_num = %i)", i, segnum, sidenum, Segments[segnum].sides[sidenum].wall_num); } for (unsigned i=0; isides[j]; if (sidep->wall_num != -1) { if (wall_flags[sidep->wall_num]) err_printf(my_file, "Error: Wall %i appears in two or more segments, including segment %i, side %i.", sidep->wall_num, i, j); else wall_flags[sidep->wall_num] = 1; } } } } // ---------------------------------------------------------------------------- static void write_player_text(PHYSFS_file *my_file) { int i, num_players=0; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Players:\n"); for (i=0; i<=Highest_object_index; i++) { if (Objects[i].type == OBJ_PLAYER) { num_players++; PHYSFSX_printf(my_file, "Player %2i is object #%3i in segment #%3i.\n", get_player_id(&Objects[i]), i, Objects[i].segnum); } } #if defined(DXX_BUILD_DESCENT_II) if (num_players != MAX_PLAYERS) err_printf(my_file, "Error: %i player objects. %i are required.", num_players, MAX_PLAYERS); #endif if (num_players > MAX_MULTI_PLAYERS) err_printf(my_file, "Error: %i player objects. %i are required.", num_players, MAX_PLAYERS); } // ---------------------------------------------------------------------------- static void write_trigger_text(PHYSFS_file *my_file) { int i, j, w; PHYSFSX_printf(my_file, "-----------------------------------------------------------------------------\n"); PHYSFSX_printf(my_file, "Triggers:\n"); for (i=0; i 4); Assert (filename[namelen-4] == '.'); strcpy(my_filename, filename); strcpy( &my_filename[namelen-4], ".txm"); my_file = PHYSFSX_openWriteBuffered( my_filename ); if (!my_file) { gr_palette_load(gr_palette); nm_messagebox( NULL, 1, "Ok", "ERROR: Unable to open %s\nErrno = %i", my_filename, errno); return; } dump_used_textures_level(my_file, 0); say_totals(my_file, Gamesave_current_filename); PHYSFSX_printf(my_file, "\nNumber of segments: %4i\n", Highest_segment_index+1); PHYSFSX_printf(my_file, "Number of objects: %4i\n", Highest_object_index+1); PHYSFSX_printf(my_file, "Number of walls: %4i\n", Num_walls); PHYSFSX_printf(my_file, "Number of open doors: %4i\n", Num_open_doors); PHYSFSX_printf(my_file, "Number of triggers: %4i\n", Num_triggers); PHYSFSX_printf(my_file, "Number of matcens: %4i\n", Num_robot_centers); PHYSFSX_printf(my_file, "\n"); write_segment_text(my_file); write_fuelcen_text(my_file); write_matcen_text(my_file); write_player_text(my_file); write_wall_text(my_file); write_trigger_text(my_file); write_exit_text(my_file); // ---------- Find control center segment ---------- write_control_center_text(my_file); // ---------- Show keyed walls ---------- write_key_text(my_file); { int r; r = PHYSFS_close(my_file); if (!r) Int3(); } } #if defined(DXX_BUILD_DESCENT_II) // Adam: Change NUM_ADAM_LEVELS to the number of levels. #define NUM_ADAM_LEVELS 30 // Adam: Stick the names here. static const char Adam_level_names[NUM_ADAM_LEVELS][13] = { "D2LEVA-1.LVL", "D2LEVA-2.LVL", "D2LEVA-3.LVL", "D2LEVA-4.LVL", "D2LEVA-S.LVL", "D2LEVB-1.LVL", "D2LEVB-2.LVL", "D2LEVB-3.LVL", "D2LEVB-4.LVL", "D2LEVB-S.LVL", "D2LEVC-1.LVL", "D2LEVC-2.LVL", "D2LEVC-3.LVL", "D2LEVC-4.LVL", "D2LEVC-S.LVL", "D2LEVD-1.LVL", "D2LEVD-2.LVL", "D2LEVD-3.LVL", "D2LEVD-4.LVL", "D2LEVD-S.LVL", "D2LEVE-1.LVL", "D2LEVE-2.LVL", "D2LEVE-3.LVL", "D2LEVE-4.LVL", "D2LEVE-S.LVL", "D2LEVF-1.LVL", "D2LEVF-2.LVL", "D2LEVF-3.LVL", "D2LEVF-4.LVL", "D2LEVF-S.LVL", }; int Ignore_tmap_num2_error; #endif // ---------------------------------------------------------------------------- static void determine_used_textures_level(int load_level_flag, int shareware_flag, int level_num, int *tmap_buf, int *wall_buf, sbyte *level_tmap_buf, int max_tmap) { int sidenum; int i, j; #if defined(DXX_BUILD_DESCENT_I) for (i=0; isides[sidenum]; if (sidep->wall_num != wall_none) { int clip_num = Walls[sidep->wall_num].clip_num; if (clip_num != -1) { int num_frames = WallAnims[clip_num].num_frames; wall_buf[clip_num] = 1; for (j=0; jtmap_num >= 0) { if (sidep->tmap_num < max_tmap) { tmap_buf[sidep->tmap_num]++; if (level_tmap_buf[sidep->tmap_num] == -1) level_tmap_buf[sidep->tmap_num] = level_num + (!shareware_flag) * NUM_SHAREWARE_LEVELS; } else { Int3(); // Error, bogus texture map. Should not be greater than max_tmap. } } if ((sidep->tmap_num2 & 0x3fff) != 0) { if ((sidep->tmap_num2 & 0x3fff) < max_tmap) { tmap_buf[sidep->tmap_num2 & 0x3fff]++; if (level_tmap_buf[sidep->tmap_num2 & 0x3fff] == -1) level_tmap_buf[sidep->tmap_num2 & 0x3fff] = 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) int objnum=max_tmap; Assert(shareware_flag != -17); for (i=0; irender_type == RT_POLYOBJ) { polymodel *po = &Polygon_models[objp->rtype.pobj_info.model_num]; for (i=0; in_textures; i++) { int tli = ObjBitmaps[ObjBitmapPtrs[po->first_texture+i]].index; if ((tli < MAX_BITMAP_FILES) && (tli >= 0)) { 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. for (segnum_t segnum=0; segnum<=Highest_segment_index; segnum++) { segment *segp = &Segments[segnum]; for (sidenum=0; sidenumsides[sidenum]; if (sidep->wall_num != wall_none) { int clip_num = Walls[sidep->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. int tmap_num; tmap_num = Textures[WallAnims[clip_num].frames[j]].index; Assert((tmap_num >= 0) && (tmap_num < MAX_BITMAP_FILES)); tmap_buf[tmap_num]++; if (level_tmap_buf[tmap_num] == -1) level_tmap_buf[tmap_num] = level_num; } } } else if (segp->children[sidenum] == segment_none) { if (sidep->tmap_num >= 0) { if (sidep->tmap_num < MAX_BITMAP_FILES) { Assert(Textures[sidep->tmap_num].index < MAX_BITMAP_FILES); tmap_buf[Textures[sidep->tmap_num].index]++; if (level_tmap_buf[Textures[sidep->tmap_num].index] == -1) level_tmap_buf[Textures[sidep->tmap_num].index] = level_num; } else Int3(); // Error, bogus texture map. Should not be greater than max_tmap. } if ((sidep->tmap_num2 & 0x3fff) != 0) { if ((sidep->tmap_num2 & 0x3fff) < MAX_BITMAP_FILES) { Assert(Textures[sidep->tmap_num2 & 0x3fff].index < MAX_BITMAP_FILES); tmap_buf[Textures[sidep->tmap_num2 & 0x3fff].index]++; if (level_tmap_buf[Textures[sidep->tmap_num2 & 0x3fff].index] == -1) level_tmap_buf[Textures[sidep->tmap_num2 & 0x3fff].index] = 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; sidep->tmap_num2 = 0; } } } } } #endif } // ---------------------------------------------------------------------------- static void merge_buffers(int *dest, int *src, int num) { int i; for (i=0; i(TmapInfo[i].filename), tb[i]); if (count++ >= 4) { PHYSFSX_printf(my_file, "\n"); count = 0; } } #elif defined(DXX_BUILD_DESCENT_II) for (i=0; i= NUM_SHAREWARE_LEVELS) { Assert((level_num - NUM_SHAREWARE_LEVELS >= 0) && (level_num - NUM_SHAREWARE_LEVELS < NUM_REGISTERED_LEVELS)); level_name = Registered_level_names[level_num - NUM_SHAREWARE_LEVELS]; } else { Assert((level_num >= 0) && (level_num < NUM_SHAREWARE_LEVELS)); level_name = Shareware_level_names[level_num]; } PHYSFSX_printf(my_file, "Texture %3i %8s used only once on level %s\n", i, static_cast(TmapInfo[i].filename), level_name); } } #endif // ---------------------------------------------------------------------------- static void say_unused_tmaps(PHYSFS_file *my_file, int *tb) { int i; int count = 0; #if defined(DXX_BUILD_DESCENT_I) const unsigned bound = Num_tmaps; #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) PHYSFSX_printf(my_file, "U"); else PHYSFSX_printf(my_file, " "); #if defined(DXX_BUILD_DESCENT_I) PHYSFSX_printf(my_file, "[%3i %8s] ", i, static_cast(TmapInfo[i].filename)); #elif defined(DXX_BUILD_DESCENT_II) PHYSFSX_printf(my_file, "[%3i %8s] ", i, AllBitmaps[i].name); #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, int *tb) { int i; for (i=0; i used_objects; while (objects_processed < Highest_object_index+1) { int objtype, objid, objcount, cur_obj_val, min_obj_val; // Find new min objnum. min_obj_val = 0x7fff0000; objnum_t min_objnum = object_none; for (objnum_t j=0; j<=Highest_object_index; j++) { if (!used_objects[j] && Objects[j].type!=OBJ_NONE) { cur_obj_val = Objects[j].type * 1000 + Objects[j].id; if (cur_obj_val < min_obj_val) { min_objnum = j; min_obj_val = cur_obj_val; } } } if ((min_objnum == object_none) || (Objects[min_objnum].type == 255)) break; objcount = 0; objtype = Objects[min_objnum].type; objid = Objects[min_objnum].id; for (i=0; i<=Highest_object_index; i++) { if (!used_objects[i]) { if (((Objects[i].type == objtype) && (Objects[i].id == objid)) || ((Objects[i].type == objtype) && (objtype == OBJ_PLAYER)) || ((Objects[i].type == objtype) && (objtype == OBJ_COOP)) || ((Objects[i].type == objtype) && (objtype == OBJ_HOSTAGE))) { if (Objects[i].type == OBJ_ROBOT) total_robots++; used_objects[i] = true; objcount++; objects_processed++; } } } if (objcount) { PHYSFSX_printf(my_file, "Object: "); PHYSFSX_printf(my_file, "%8s %8s %3i\n", object_types(min_objnum), object_ids(min_objnum), objcount); } } PHYSFSX_printf(my_file, "Total robots = %3i\n", total_robots); } #if defined(DXX_BUILD_DESCENT_II) int First_dump_level = 0; int Last_dump_level = NUM_ADAM_LEVELS-1; #endif // ---------------------------------------------------------------------------- static void say_totals_all(void) { int i; PHYSFS_file *my_file; my_file = PHYSFSX_openWriteBuffered( "levels.all" ); if (!my_file) { gr_palette_load(gr_palette); nm_messagebox( NULL, 1, "Ok", "ERROR: Unable to open levels.all\nErrno=%i", errno ); return; } #if defined(DXX_BUILD_DESCENT_I) for (i=0; i