dxx-rebirth/similar/main/mission.cpp

1611 lines
50 KiB
C++
Raw Normal View History

2006-03-20 17:12:09 +00:00
/*
2014-06-01 17:55:23 +00:00
* 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.
2006-03-20 17:12:09 +00:00
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.
*/
/*
*
* Code to handle multiple missions
*
*/
#include <algorithm>
2014-08-15 22:59:54 +00:00
#include <vector>
2006-03-20 17:12:09 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include "pstypes.h"
#include "strutil.h"
#include "inferno.h"
#include "window.h"
2006-03-20 17:12:09 +00:00
#include "mission.h"
#include "gamesave.h"
#include "piggy.h"
#include "console.h"
2014-11-23 04:36:58 +00:00
#include "polyobj.h"
#include "dxxerror.h"
2006-03-20 17:12:09 +00:00
#include "config.h"
#include "newmenu.h"
#include "text.h"
#include "u_mem.h"
#include "ignorecase.h"
2012-11-11 00:14:30 +00:00
#include "physfsx.h"
2015-04-26 20:15:50 +00:00
#include "physfs_list.h"
#include "event.h"
2012-11-11 00:14:30 +00:00
#if defined(DXX_BUILD_DESCENT_II)
#include "movie.h"
#endif
#include "null_sentinel_iterator.h"
2006-03-20 17:12:09 +00:00
#include "compiler-poison.h"
2014-08-15 22:59:54 +00:00
#include "compiler-range_for.h"
#include "d_enumerate.h"
#include <memory>
2014-08-24 03:36:35 +00:00
#define BIMD1_BRIEFING_FILE "briefing.txb"
using std::min;
2015-10-11 22:21:00 +00:00
#define MISSION_EXTENSION_DESCENT_I ".msn"
#if defined(DXX_BUILD_DESCENT_II)
#define MISSION_EXTENSION_DESCENT_II ".mn2"
#endif
#define CON_PRIORITY_DEBUG_MISSION_LOAD CON_DEBUG
namespace {
2020-05-02 21:18:42 +00:00
using mission_candidate_search_path = std::array<char, PATH_MAX>;
}
namespace dsx {
namespace {
struct mle;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
using mission_list_type = std::vector<mle>;
2015-10-11 22:21:00 +00:00
2015-01-13 04:19:42 +00:00
//mission list entry
2015-01-15 04:30:03 +00:00
struct mle : Mission_path
2015-01-13 04:19:42 +00:00
{
2006-03-20 17:12:09 +00:00
int builtin_hogsize; // if it's the built-in mission, used for determining the version
ntstring<75> mission_name;
#if defined(DXX_BUILD_DESCENT_II)
2015-10-18 21:01:18 +00:00
descent_version_type descent_version; // descent 1 or descent 2?
#endif
2006-03-20 17:12:09 +00:00
ubyte anarchy_only_flag; // if true, mission is anarchy only
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
mission_list_type directory;
mle(Mission_path &&m) :
Mission_path(std::move(m))
{
}
mle(const char *const name, std::vector<mle> &&d);
};
2006-03-20 17:12:09 +00:00
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
struct mission_subdir_stats
{
std::size_t immediate_directories = 0, immediate_missions = 0, total_missions = 0;
static std::size_t count_missions(const mission_list_type &directory)
{
std::size_t total_missions = 0;
range_for (auto &&i, directory)
{
if (i.directory.empty())
++ total_missions;
else
total_missions += count_missions(i.directory);
}
return total_missions;
}
void count(const mission_list_type &directory)
{
range_for (auto &&i, directory)
{
if (i.directory.empty())
{
++ total_missions;
++ immediate_missions;
}
else
{
++ immediate_directories;
total_missions += count_missions(i.directory);
}
}
}
};
struct mission_name_and_version
{
#if defined(DXX_BUILD_DESCENT_II)
const Mission::descent_version_type descent_version = {};
#endif
char *const name = nullptr;
mission_name_and_version() = default;
mission_name_and_version(Mission::descent_version_type, char *);
};
mission_name_and_version::mission_name_and_version(Mission::descent_version_type const v, char *const n) :
#if defined(DXX_BUILD_DESCENT_II)
descent_version(v),
#endif
name(n)
{
#if defined(DXX_BUILD_DESCENT_I)
(void)v;
#endif
}
2020-05-02 21:18:42 +00:00
const char *prepare_mission_list_count_dirbuf(std::array<char, 12> &dirbuf, const std::size_t immediate_directories)
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
{
/* Limit the count of directories to what can be formatted
* successfully without truncation. If a user has more than this
* many directories, an empty string will be used instead of showing
* the actual count.
*/
if (immediate_directories && immediate_directories <= 99999)
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
{
2018-08-03 04:08:12 +00:00
snprintf(dirbuf.data(), dirbuf.size(), "DIR:%zu; ", immediate_directories);
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
return dirbuf.data();
}
return "";
}
mle::mle(const char *const name, std::vector<mle> &&d) :
Mission_path(name, 0), directory(std::move(d))
{
mission_subdir_stats ss;
ss.count(directory);
2020-05-02 21:18:42 +00:00
std::array<char, 12> dirbuf;
2018-08-03 04:08:12 +00:00
snprintf(mission_name.data(), mission_name.size(), "%s/ [%sMSN:L%zu;T%zu]", name, prepare_mission_list_count_dirbuf(dirbuf, ss.immediate_directories), ss.immediate_missions, ss.total_missions);
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
}
static const mle *compare_mission_predicate_to_leaf(const mission_entry_predicate mission_predicate, const mle &candidate, const char *candidate_filesystem_name)
{
#if defined(DXX_BUILD_DESCENT_II)
if (mission_predicate.check_version && mission_predicate.descent_version != candidate.descent_version)
{
con_printf(CON_PRIORITY_DEBUG_MISSION_LOAD, DXX_STRINGIZE_FL(__FILE__, __LINE__, "mission version check requires %u, but found %u; skipping string comparison for mission \"%s\""), static_cast<unsigned>(mission_predicate.descent_version), static_cast<unsigned>(candidate.descent_version), candidate.path.data());
return nullptr;
}
#endif
if (!d_stricmp(mission_predicate.filesystem_name, candidate_filesystem_name))
{
con_printf(CON_PRIORITY_DEBUG_MISSION_LOAD, DXX_STRINGIZE_FL(__FILE__, __LINE__, "found mission \"%s\"[\"%s\"] at %p"), candidate.path.data(), &*candidate.filename, &candidate);
return &candidate;
}
con_printf(CON_PRIORITY_DEBUG_MISSION_LOAD, DXX_STRINGIZE_FL(__FILE__, __LINE__, "want mission \"%s\", no match for mission \"%s\"[\"%s\"] at %p"), mission_predicate.filesystem_name, candidate.path.data(), &*candidate.filename, &candidate);
return nullptr;
}
static const mle *compare_mission_by_guess(const mission_entry_predicate mission_predicate, const mle &candidate)
{
if (candidate.directory.empty())
return compare_mission_predicate_to_leaf(mission_predicate, candidate, &*candidate.filename);
{
const unsigned long size = candidate.directory.size();
con_printf(CON_PRIORITY_DEBUG_MISSION_LOAD, DXX_STRINGIZE_FL(__FILE__, __LINE__, "want mission \"%s\", check %lu missions under \"%s\""), mission_predicate.filesystem_name, size, candidate.path.data());
}
range_for (auto &i, candidate.directory)
{
if (const auto r = compare_mission_by_guess(mission_predicate, i))
return r;
}
con_printf(CON_PRIORITY_DEBUG_MISSION_LOAD, DXX_STRINGIZE_FL(__FILE__, __LINE__, "no matches under \"%s\""), candidate.path.data());
return nullptr;
}
static const mle *compare_mission_by_pathname(const mission_entry_predicate mission_predicate, const mle &candidate)
{
if (candidate.directory.empty())
return compare_mission_predicate_to_leaf(mission_predicate, candidate, candidate.path.data());
const auto mission_name = mission_predicate.filesystem_name;
const auto path_length = candidate.path.size();
if (!strncmp(mission_name, candidate.path.data(), path_length) && mission_name[path_length] == '/')
{
{
const unsigned long size = candidate.directory.size();
con_printf(CON_PRIORITY_DEBUG_MISSION_LOAD, DXX_STRINGIZE_FL(__FILE__, __LINE__, "want mission pathname \"%s\", check %lu missions under \"%s\""), mission_predicate.filesystem_name, size, candidate.path.data());
}
range_for (auto &i, candidate.directory)
{
if (const auto r = compare_mission_by_pathname(mission_predicate, i))
return r;
}
con_printf(CON_PRIORITY_DEBUG_MISSION_LOAD, DXX_STRINGIZE_FL(__FILE__, __LINE__, "no matches under \"%s\""), candidate.path.data());
}
else
con_printf(CON_PRIORITY_DEBUG_MISSION_LOAD, DXX_STRINGIZE_FL(__FILE__, __LINE__, "want mission pathname \"%s\", ignore non-matching directory \"%s\""), mission_predicate.filesystem_name, candidate.path.data());
return nullptr;
}
}
2016-05-22 17:49:32 +00:00
}
2006-03-20 17:12:09 +00:00
2014-07-20 22:13:25 +00:00
Mission_ptr Current_mission; // currently loaded mission
2006-03-20 17:12:09 +00:00
2015-01-12 00:26:03 +00:00
static bool null_or_space(char c)
{
return !c || isspace(static_cast<unsigned>(c));
}
// Allocate the Level_names, Secret_level_names and Secret_level_table arrays
static int allocate_levels(void)
{
2020-05-02 21:18:42 +00:00
Level_names = std::make_unique<d_fname[]>(Last_level);
if (Last_secret_level)
{
N_secret_levels = -Last_secret_level;
2020-05-02 21:18:42 +00:00
Secret_level_names = std::make_unique<d_fname[]>(N_secret_levels);
Secret_level_table = std::make_unique<ubyte[]>(N_secret_levels);
}
return 1;
}
2006-03-20 17:12:09 +00:00
//
// Special versions of mission routines for d1 builtins
//
static const char *load_mission_d1()
2006-03-20 17:12:09 +00:00
{
switch (PHYSFSX_fsize("descent.hog"))
2007-10-29 21:40:49 +00:00
{
case D1_SHAREWARE_MISSION_HOGSIZE:
case D1_SHAREWARE_10_MISSION_HOGSIZE:
N_secret_levels = 0;
Last_level = 7;
Last_secret_level = 0;
if (!allocate_levels())
{
2014-07-20 22:13:25 +00:00
Current_mission.reset();
return "Failed to allocate level memory for Descent 1 shareware";
}
2007-10-29 21:40:49 +00:00
//build level names
for (int i=0;i<Last_level;i++)
2014-07-26 22:45:01 +00:00
snprintf(&Level_names[i][0u], Level_names[i].size(), "level%02d.sdl", i+1);
2014-07-23 02:27:22 +00:00
Briefing_text_filename = BIMD1_BRIEFING_FILE;
Ending_text_filename = BIMD1_ENDING_FILE_SHARE;
2007-10-29 21:40:49 +00:00
break;
case D1_MAC_SHARE_MISSION_HOGSIZE:
N_secret_levels = 0;
Last_level = 3;
Last_secret_level = 0;
if (!allocate_levels())
{
2014-07-20 22:13:25 +00:00
Current_mission.reset();
return "Failed to allocate level memory for Descent 1 Mac shareware";
}
2007-10-29 21:40:49 +00:00
//build level names
for (int i=0;i<Last_level;i++)
2014-07-26 22:45:01 +00:00
snprintf(&Level_names[i][0u], Level_names[i].size(), "level%02d.sdl", i+1);
2014-07-23 02:27:22 +00:00
Briefing_text_filename = BIMD1_BRIEFING_FILE;
Ending_text_filename = BIMD1_ENDING_FILE_SHARE;
2007-10-29 21:40:49 +00:00
break;
case D1_OEM_MISSION_HOGSIZE:
case D1_OEM_10_MISSION_HOGSIZE:
{
2007-10-29 21:40:49 +00:00
N_secret_levels = 1;
constexpr unsigned last_level = 15;
constexpr int last_secret_level = -1;
Last_level = last_level;
Last_secret_level = last_secret_level;
2007-10-29 21:40:49 +00:00
if (!allocate_levels())
{
2014-07-20 22:13:25 +00:00
Current_mission.reset();
return "Failed to allocate level memory for Descent 1 OEM";
}
2007-10-29 21:40:49 +00:00
//build level names
for (unsigned i = 0; i < last_level - 1; ++i)
{
auto &ln = Level_names[i];
snprintf(&ln[0u], ln.size(), "level%02u.rdl", i + 1);
}
{
auto &ln = Level_names[last_level - 1];
snprintf(&ln[0u], ln.size(), "saturn%02d.rdl", last_level);
}
for (int i = 0; i < -last_secret_level; ++i)
{
auto &sn = Secret_level_names[i];
snprintf(&sn[0u], sn.size(), "levels%1d.rdl", i + 1);
}
2007-10-29 21:40:49 +00:00
Secret_level_table[0] = 10;
Briefing_text_filename = "briefsat.txb";
2014-07-23 02:27:22 +00:00
Ending_text_filename = BIMD1_ENDING_FILE_OEM;
}
2007-10-29 21:40:49 +00:00
break;
default:
Int3();
DXX_BOOST_FALLTHROUGH;
2007-10-29 21:40:49 +00:00
case D1_MISSION_HOGSIZE:
case D1_MISSION_HOGSIZE2:
2007-10-29 21:40:49 +00:00
case D1_10_MISSION_HOGSIZE:
case D1_MAC_MISSION_HOGSIZE:
{
2007-10-29 21:40:49 +00:00
N_secret_levels = 3;
constexpr unsigned last_level = BIMD1_LAST_LEVEL;
constexpr int last_secret_level = BIMD1_LAST_SECRET_LEVEL;
Last_level = last_level;
Last_secret_level = last_secret_level;
2007-10-29 21:40:49 +00:00
if (!allocate_levels())
{
2014-07-20 22:13:25 +00:00
Current_mission.reset();
return "Failed to allocate level memory for Descent 1";
}
2007-10-29 21:40:49 +00:00
//build level names
for (unsigned i = 0; i < last_level; ++i)
{
auto &ln = Level_names[i];
snprintf(&ln[0u], ln.size(), "level%02u.rdl", i + 1);
}
for (int i = 0; i < -last_secret_level; ++i)
{
auto &sn = Secret_level_names[i];
snprintf(&sn[0u], sn.size(), "levels%1d.rdl", i + 1);
}
2007-10-29 21:40:49 +00:00
Secret_level_table[0] = 10;
Secret_level_table[1] = 21;
Secret_level_table[2] = 24;
2014-07-23 02:27:22 +00:00
Briefing_text_filename = BIMD1_BRIEFING_FILE;
Ending_text_filename = "endreg.txb";
2007-10-29 21:40:49 +00:00
break;
}
2006-03-20 17:12:09 +00:00
}
return nullptr;
2006-03-20 17:12:09 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
//
// Special versions of mission routines for shareware
//
static const char *load_mission_shareware()
2006-03-20 17:12:09 +00:00
{
2014-12-22 04:35:48 +00:00
Current_mission->mission_name.copy_if(SHAREWARE_MISSION_NAME);
Current_mission->descent_version = Mission::descent_version_type::descent2;
2006-03-20 17:12:09 +00:00
Current_mission->anarchy_only_flag = 0;
switch (Current_mission->builtin_hogsize)
{
case MAC_SHARE_MISSION_HOGSIZE:
N_secret_levels = 1;
2006-03-20 17:12:09 +00:00
Last_level = 4;
Last_secret_level = -1;
2006-03-20 17:12:09 +00:00
if (!allocate_levels())
{
2014-07-20 22:13:25 +00:00
Current_mission.reset();
return "Failed to allocate level memory for Descent 2 Mac shareware";
}
// mac demo is using the regular hog and rl2 files
2014-07-23 02:27:22 +00:00
Level_names[0] = "d2leva-1.rl2";
Level_names[1] = "d2leva-2.rl2";
Level_names[2] = "d2leva-3.rl2";
Level_names[3] = "d2leva-4.rl2";
Secret_level_names[0] = "d2leva-s.rl2";
break;
default:
Int3();
DXX_BOOST_FALLTHROUGH;
case SHAREWARE_MISSION_HOGSIZE:
N_secret_levels = 0;
2006-03-20 17:12:09 +00:00
Last_level = 3;
Last_secret_level = 0;
2006-03-20 17:12:09 +00:00
if (!allocate_levels())
{
2014-07-20 22:13:25 +00:00
Current_mission.reset();
return "Failed to allocate level memory for Descent 2 shareware";
}
2014-07-23 02:27:22 +00:00
Level_names[0] = "d2leva-1.sl2";
Level_names[1] = "d2leva-2.sl2";
Level_names[2] = "d2leva-3.sl2";
2006-03-20 17:12:09 +00:00
}
return nullptr;
2006-03-20 17:12:09 +00:00
}
//
// Special versions of mission routines for Diamond/S3 version
//
static const char *load_mission_oem()
2006-03-20 17:12:09 +00:00
{
2014-12-22 04:35:48 +00:00
Current_mission->mission_name.copy_if(OEM_MISSION_NAME);
Current_mission->descent_version = Mission::descent_version_type::descent2;
2006-03-20 17:12:09 +00:00
Current_mission->anarchy_only_flag = 0;
N_secret_levels = 2;
Last_level = 8;
Last_secret_level = -2;
if (!allocate_levels())
{
2014-07-20 22:13:25 +00:00
Current_mission.reset();
return "Failed to allocate level memory for Descent 2 OEM";
}
2014-07-23 02:27:22 +00:00
Level_names[0] = "d2leva-1.rl2";
Level_names[1] = "d2leva-2.rl2";
Level_names[2] = "d2leva-3.rl2";
Level_names[3] = "d2leva-4.rl2";
Secret_level_names[0] = "d2leva-s.rl2";
Level_names[4] = "d2levb-1.rl2";
Level_names[5] = "d2levb-2.rl2";
Level_names[6] = "d2levb-3.rl2";
Level_names[7] = "d2levb-4.rl2";
Secret_level_names[1] = "d2levb-s.rl2";
2006-03-20 17:12:09 +00:00
Secret_level_table[0] = 1;
Secret_level_table[1] = 5;
return nullptr;
2006-03-20 17:12:09 +00:00
}
#endif
2006-03-20 17:12:09 +00:00
//compare a string for a token. returns true if match
2013-10-27 22:00:14 +00:00
static int istok(const char *buf,const char *tok)
2006-03-20 17:12:09 +00:00
{
return d_strnicmp(buf,tok,strlen(tok)) == 0;
2006-03-20 17:12:09 +00:00
}
//returns ptr to string after '=' & white space, or NULL if no '='
//adds 0 after parm at first white space
2013-10-27 22:00:14 +00:00
static char *get_value(char *buf)
2006-03-20 17:12:09 +00:00
{
2014-07-20 20:56:39 +00:00
char *t = strchr(buf,'=');
2006-03-20 17:12:09 +00:00
if (t) {
2014-07-20 20:56:39 +00:00
while (isspace(static_cast<unsigned>(*++t)));
2006-03-20 17:12:09 +00:00
if (*t)
return t;
}
return NULL; //error!
}
static mission_name_and_version get_any_mission_type_name_value(PHYSFSX_gets_line_t<80> &buf, PHYSFS_File *const f, const Mission::descent_version_type descent_version)
2006-03-20 17:12:09 +00:00
{
if (!PHYSFSX_fgets(buf,f))
return {};
if (istok(buf, "name"))
return {descent_version, get_value(buf)};
#if defined(DXX_BUILD_DESCENT_II)
if (descent_version == Mission::descent_version_type::descent1)
/* If reading a Descent 1 `.msn` file, do not check for the
* extended mission types. D1X-Rebirth would ignore them, so
* D2X-Rebirth should also ignore them.
*/
return {};
struct name_type_pair
{
/* std::pair cannot be used here because direct initialization
* from a string literal fails to compile.
*/
char name[7];
Mission::descent_version_type descent_version;
};
static constexpr name_type_pair mission_name_type_values[] = {
{"xname", Mission::descent_version_type::descent2x}, // enhanced mission
{"zname", Mission::descent_version_type::descent2z}, // super-enhanced mission
{"!name", Mission::descent_version_type::descent2a}, // extensible-enhanced mission
};
range_for (const auto &parm, mission_name_type_values)
{
if (istok(buf, parm.name))
return {parm.descent_version, get_value(buf)};
}
#endif
return {};
2006-03-20 17:12:09 +00:00
}
2014-08-30 22:38:26 +00:00
static bool ml_sort_func(const mle &e0,const mle &e1)
2006-03-20 17:12:09 +00:00
{
const auto d0 = e0.directory.empty();
const auto d1 = e1.directory.empty();
if (d0 != d1)
/* If d0 is a directory and d1 is a mission, or if d0 is a
* mission and d1 is a directory, then apply a special case.
*
* Consider d0 to be less (and therefore ordered earlier) if d1
* is a mission. This moves directories to the top of the list.
*/
return d1;
/* If both d0 and d1 are directories, or if both are missions, then
* apply the usual sorting rule. This makes directories sort
* as usual relative to each other.
*/
2014-08-30 22:38:26 +00:00
return d_stricmp(e0.mission_name,e1.mission_name) < 0;
2006-03-20 17:12:09 +00:00
}
//returns 1 if file read ok, else 0
namespace dsx {
2016-05-22 17:49:32 +00:00
static int read_mission_file(mission_list_type &mission_list, mission_candidate_search_path &pathname)
2006-03-20 17:12:09 +00:00
{
if (const auto mfile = PHYSFSX_openReadBuffered(pathname.data()))
{
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
std::string str_pathname = pathname.data();
const auto idx_last_slash = str_pathname.find_last_of('/');
const auto idx_filename = (idx_last_slash == str_pathname.npos) ? 0 : idx_last_slash + 1;
const auto idx_file_extension = str_pathname.find_first_of('.', idx_filename);
if (idx_file_extension == str_pathname.npos)
2006-03-20 17:12:09 +00:00
return 0; //missing extension
if (idx_file_extension >= DXX_MAX_MISSION_PATH_LENGTH)
return 0; // path too long, would be truncated in save game files
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
str_pathname.resize(idx_file_extension);
mission_list.emplace_back(Mission_path(std::move(str_pathname), idx_filename));
2014-08-15 22:59:54 +00:00
mle *mission = &mission_list.back();
#if defined(DXX_BUILD_DESCENT_I)
constexpr auto descent_version = Mission::descent_version_type::descent1;
#elif defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// look if it's .mn2 or .msn
auto descent_version = (pathname[idx_file_extension + 3] == MISSION_EXTENSION_DESCENT_II[3])
? Mission::descent_version_type::descent2
: Mission::descent_version_type::descent1;
#endif
2006-03-20 17:12:09 +00:00
mission->anarchy_only_flag = 0;
2014-09-07 19:48:10 +00:00
PHYSFSX_gets_line_t<80> buf;
const auto &&nv = get_any_mission_type_name_value(buf, mfile, descent_version);
2006-03-20 17:12:09 +00:00
if (const auto p = nv.name) {
#if defined(DXX_BUILD_DESCENT_II)
mission->descent_version = nv.descent_version;
#endif
2006-03-20 17:12:09 +00:00
char *t;
if ((t=strchr(p,';'))!=NULL)
{
2006-03-20 17:12:09 +00:00
*t=0;
--t;
}
else
t = p + strlen(p) - 1;
while (isspace(static_cast<unsigned>(*t)))
2006-03-20 17:12:09 +00:00
*t-- = 0; // remove trailing whitespace
2014-12-22 04:35:48 +00:00
mission->mission_name.copy_if(p, mission->mission_name.size() - 1);
2006-03-20 17:12:09 +00:00
}
else {
2014-08-15 22:59:54 +00:00
mission_list.pop_back();
2006-03-20 17:12:09 +00:00
return 0;
}
2014-09-07 19:48:10 +00:00
{
PHYSFSX_gets_line_t<4096> temp;
if (PHYSFSX_fgets(temp,mfile))
{
if (istok(temp,"type"))
{
const auto p = get_value(temp);
//get mission type
if (p)
mission->anarchy_only_flag = istok(p,"anarchy");
}
}
2014-09-07 19:48:10 +00:00
}
2006-03-20 17:12:09 +00:00
return 1;
}
return 0;
}
}
2006-03-20 17:12:09 +00:00
namespace dsx {
2016-05-22 17:49:32 +00:00
static void add_d1_builtin_mission_to_list(mission_list_type &mission_list)
2006-03-20 17:12:09 +00:00
{
int size;
size = PHYSFSX_fsize("descent.hog");
if (size == -1)
return;
2006-03-20 17:12:09 +00:00
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
mission_list.emplace_back(Mission_path(D1_MISSION_FILENAME, 0));
2014-08-15 22:59:54 +00:00
mle *mission = &mission_list.back();
2006-03-20 17:12:09 +00:00
switch (size) {
case D1_SHAREWARE_MISSION_HOGSIZE:
case D1_SHAREWARE_10_MISSION_HOGSIZE:
case D1_MAC_SHARE_MISSION_HOGSIZE:
2014-12-22 04:35:48 +00:00
mission->mission_name.copy_if(D1_SHAREWARE_MISSION_NAME);
2006-03-20 17:12:09 +00:00
mission->anarchy_only_flag = 0;
break;
case D1_OEM_MISSION_HOGSIZE:
case D1_OEM_10_MISSION_HOGSIZE:
2014-12-22 04:35:48 +00:00
mission->mission_name.copy_if(D1_OEM_MISSION_NAME);
2006-03-20 17:12:09 +00:00
mission->anarchy_only_flag = 0;
break;
default:
Warning("Unknown D1 hogsize %d\n", size);
Int3();
DXX_BOOST_FALLTHROUGH;
2006-03-20 17:12:09 +00:00
case D1_MISSION_HOGSIZE:
case D1_MISSION_HOGSIZE2:
2006-03-20 17:12:09 +00:00
case D1_10_MISSION_HOGSIZE:
case D1_MAC_MISSION_HOGSIZE:
2014-12-22 04:35:48 +00:00
mission->mission_name.copy_if(D1_MISSION_NAME);
2006-03-20 17:12:09 +00:00
mission->anarchy_only_flag = 0;
break;
}
mission->anarchy_only_flag = 0;
#if defined(DXX_BUILD_DESCENT_I)
mission->builtin_hogsize = size;
#elif defined(DXX_BUILD_DESCENT_II)
mission->descent_version = Mission::descent_version_type::descent1;
2006-03-20 17:12:09 +00:00
mission->builtin_hogsize = 0;
#endif
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
2014-08-15 22:59:54 +00:00
template <std::size_t N1, std::size_t N2>
2016-05-22 17:49:32 +00:00
static void set_hardcoded_mission(mission_list_type &mission_list, const char (&path)[N1], const char (&mission_name)[N2])
2014-08-15 22:59:54 +00:00
{
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
mission_list.emplace_back(Mission_path(path, 0));
2014-08-15 22:59:54 +00:00
mle *mission = &mission_list.back();
2014-12-22 04:35:48 +00:00
mission->mission_name.copy_if(mission_name);
2014-08-15 22:59:54 +00:00
mission->anarchy_only_flag = 0;
}
2016-05-22 17:49:32 +00:00
static void add_builtin_mission_to_list(mission_list_type &mission_list, d_fname &name)
2006-03-20 17:12:09 +00:00
{
int size = PHYSFSX_fsize("descent2.hog");
2006-03-20 17:12:09 +00:00
if (size == -1)
size = PHYSFSX_fsize("d2demo.hog");
2006-03-20 17:12:09 +00:00
switch (size) {
case SHAREWARE_MISSION_HOGSIZE:
case MAC_SHARE_MISSION_HOGSIZE:
2014-08-15 22:59:54 +00:00
set_hardcoded_mission(mission_list, SHAREWARE_MISSION_FILENAME, SHAREWARE_MISSION_NAME);
2006-03-20 17:12:09 +00:00
break;
case OEM_MISSION_HOGSIZE:
2014-08-15 22:59:54 +00:00
set_hardcoded_mission(mission_list, OEM_MISSION_FILENAME, OEM_MISSION_NAME);
2006-03-20 17:12:09 +00:00
break;
default:
2015-10-11 22:21:00 +00:00
Warning("Unknown hogsize %d, trying %s\n", size, FULL_MISSION_FILENAME MISSION_EXTENSION_DESCENT_II);
Int3();
DXX_BOOST_FALLTHROUGH;
2006-03-20 17:12:09 +00:00
case FULL_MISSION_HOGSIZE:
case FULL_10_MISSION_HOGSIZE:
case MAC_FULL_MISSION_HOGSIZE:
2015-10-11 22:21:00 +00:00
{
mission_candidate_search_path full_mission_filename = {{FULL_MISSION_FILENAME MISSION_EXTENSION_DESCENT_II}};
if (!read_mission_file(mission_list, full_mission_filename))
Error("Could not find required mission file <%s>", FULL_MISSION_FILENAME MISSION_EXTENSION_DESCENT_II);
}
2006-03-20 17:12:09 +00:00
}
2014-08-15 22:59:54 +00:00
mle *mission = &mission_list.back();
2014-08-16 03:59:14 +00:00
name.copy_if(mission->path.c_str(), FILENAME_LEN);
2006-03-20 17:12:09 +00:00
mission->builtin_hogsize = size;
mission->descent_version = Mission::descent_version_type::descent2;
2006-03-20 17:12:09 +00:00
mission->anarchy_only_flag = 0;
}
#endif
2006-03-20 17:12:09 +00:00
namespace dsx {
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
static void add_missions_to_list(mission_list_type &mission_list, mission_candidate_search_path &path, const mission_candidate_search_path::iterator rel_path, const mission_filter_mode mission_filter)
2006-03-20 17:12:09 +00:00
{
2015-10-11 22:21:00 +00:00
/* rel_path must point within the array `path`.
* rel_path must point to the null that follows a possibly empty
* directory prefix.
* If the directory prefix is not empty, it must end with a PHYSFS
* path separator, which is always slash, even on Windows.
*
* If any of these assertions fail, then the path transforms used to
* recurse into subdirectories and to open individual missions will
* not work correctly.
*/
assert(std::distance(path.begin(), rel_path) < path.size() - 1);
2015-10-11 22:21:00 +00:00
assert(!*rel_path);
assert(path.begin() == rel_path || *std::prev(rel_path) == '/');
const std::size_t space_remaining = std::distance(rel_path, path.end());
*rel_path = '.';
*std::next(rel_path) = 0;
2015-10-11 22:21:00 +00:00
range_for (const auto i, PHYSFSX_uncounted_list{PHYSFS_enumerateFiles(path.data())})
2006-03-20 17:12:09 +00:00
{
2015-10-11 22:21:00 +00:00
/* Add 1 to include the terminating null. */
const std::size_t il = strlen(i) + 1;
/* Add 2 for the slash+dot in case it is a directory. */
if (il + 2 >= space_remaining)
2006-03-20 17:12:09 +00:00
continue; // path is too long
2015-10-11 22:21:00 +00:00
auto j = std::copy_n(i, il, rel_path);
const char *ext;
if (PHYSFS_isDirectory(path.data()))
2006-03-20 17:12:09 +00:00
{
const auto null = std::prev(j);
2015-10-11 22:21:00 +00:00
*j = 0;
*null = '/';
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
mission_list_type sublist;
add_missions_to_list(sublist, path, j, mission_filter);
2015-10-11 22:21:00 +00:00
*null = 0;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
const auto found = sublist.size();
if (!found)
{
/* Ignore empty directories */
}
else if (found == 1)
{
/* If only one found, promote it up to the next level so
* the user does not need to navigate into a
* single-element directory.
*/
auto &sli = sublist.front();
mission_list.emplace_back(std::move(sli));
}
else
{
std::sort(sublist.begin(), sublist.end(), ml_sort_func);
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
mission_list.emplace_back(path.data(), std::move(sublist));
}
2006-03-20 17:12:09 +00:00
}
2015-10-11 22:21:00 +00:00
else if (il > 5 &&
((ext = &i[il - 5], !d_strnicmp(ext, MISSION_EXTENSION_DESCENT_I))
#if defined(DXX_BUILD_DESCENT_II)
|| !d_strnicmp(ext, MISSION_EXTENSION_DESCENT_II)
#endif
))
if (read_mission_file(mission_list, path))
2006-03-20 17:12:09 +00:00
{
if (mission_filter != mission_filter_mode::exclude_anarchy || !mission_list.back().anarchy_only_flag)
2006-03-20 17:12:09 +00:00
{
2014-08-15 22:59:54 +00:00
mission_list.back().builtin_hogsize = 0;
2006-03-20 17:12:09 +00:00
}
else
2014-08-15 22:59:54 +00:00
mission_list.pop_back();
2006-03-20 17:12:09 +00:00
}
2014-08-15 22:59:54 +00:00
if (mission_list.size() >= MAX_MISSIONS)
2006-03-20 17:12:09 +00:00
{
break;
}
2015-10-11 22:21:00 +00:00
*rel_path = 0; // chop off the entry
DXX_POISON_MEMORY(std::next(rel_path), path.end(), 0xcc);
2006-03-20 17:12:09 +00:00
}
}
}
2006-03-20 17:12:09 +00:00
/* move <mission_name> to <place> on mission list, increment <place> */
2016-05-22 17:49:32 +00:00
static void promote (mission_list_type &mission_list, const char *const name, std::size_t &top_place)
2006-03-20 17:12:09 +00:00
{
2014-08-15 22:59:54 +00:00
range_for (auto &i, partial_range(mission_list, top_place, mission_list.size()))
2014-08-16 03:59:14 +00:00
if (!d_stricmp(&*i.filename, name)) {
2006-03-20 17:12:09 +00:00
//swap mission positions
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
auto &j = mission_list[top_place++];
if (&j != &i)
std::swap(j, i);
2006-03-20 17:12:09 +00:00
break;
}
}
Mission::~Mission()
2006-03-20 17:12:09 +00:00
{
// May become more complex with the editor
2014-08-16 03:59:14 +00:00
if (!path.empty() && builtin_hogsize == 0)
{
char hogpath[PATH_MAX];
2016-12-17 18:39:18 +00:00
snprintf(hogpath, sizeof(hogpath), "%s.hog", path.c_str());
PHYSFSX_removeRelFromSearchPath(hogpath);
}
2006-03-20 17:12:09 +00:00
}
//fills in the global list of missions. Returns the number of missions
//in the list. If anarchy_mode is set, then also add anarchy-only missions.
namespace dsx {
2006-03-20 17:12:09 +00:00
static mission_list_type build_mission_list(const mission_filter_mode mission_filter)
{
2006-03-20 17:12:09 +00:00
//now search for levels on disk
//@@Took out this code because after this routine was called once for
//@@a list of single-player missions, a subsequent call for a list of
//@@anarchy missions would not scan again, and thus would not find the
//@@anarchy-only missions. If we retain the minimum level of install,
//@@we may want to put the code back in, having it always scan for all
//@@missions, and have the code that uses it sort out the ones it wants.
//@@ if (num_missions != -1) {
//@@ if (Current_mission_num != 0)
//@@ load_mission(0); //set built-in mission as default
//@@ return num_missions;
//@@ }
2016-05-22 17:49:32 +00:00
mission_list_type mission_list;
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
d_fname builtin_mission_filename;
2014-08-15 22:59:54 +00:00
add_builtin_mission_to_list(mission_list, builtin_mission_filename); //read built-in first
#endif
2014-08-15 22:59:54 +00:00
add_d1_builtin_mission_to_list(mission_list);
2015-10-11 22:21:00 +00:00
mission_candidate_search_path search_str = {{MISSION_DIR}};
DXX_POISON_MEMORY(std::next(search_str.begin(), sizeof(MISSION_DIR)), search_str.end(), 0xcc);
add_missions_to_list(mission_list, search_str, search_str.begin() + sizeof(MISSION_DIR) - 1, mission_filter);
2006-03-20 17:12:09 +00:00
// move original missions (in story-chronological order)
// to top of mission list
2014-08-15 22:59:54 +00:00
std::size_t top_place = 0;
promote(mission_list, D1_MISSION_FILENAME, top_place); // original descent 1 mission
#if defined(DXX_BUILD_DESCENT_II)
2014-08-15 22:59:54 +00:00
promote(mission_list, builtin_mission_filename, top_place); // d2 or d2demo
promote(mission_list, "d2x", top_place); // vertigo
#endif
2006-03-20 17:12:09 +00:00
2014-08-15 22:59:54 +00:00
if (mission_list.size() > top_place)
std::sort(next(begin(mission_list), top_place), end(mission_list), ml_sort_func);
2006-03-20 17:12:09 +00:00
return mission_list;
}
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
//values for built-in mission
int load_mission_ham()
{
read_hamfile(); // intentionally can also read from the HOG
if (Piggy_hamfile_version >= 3)
{
// re-read sounds in case mission has custom .sXX
Num_sound_files = 0;
read_sndfile();
piggy_read_sounds();
}
if (Current_mission->descent_version == Mission::descent_version_type::descent2a &&
Current_mission->alternate_ham_file)
{
/*
* If an alternate HAM is specified, map a HOG of the same name
* (if it exists) so that users can reference a HAM within a
* HOG. This is required to let users reference the D2X.HAM
* file provided by Descent II: Vertigo.
*
* Try both plain NAME and missions/NAME, in that order.
*/
2014-07-23 01:52:10 +00:00
auto &altham = Current_mission->alternate_ham_file;
unsigned l = strlen(*altham);
char althog[PATH_MAX];
2014-07-23 02:27:22 +00:00
snprintf(althog, sizeof(althog), MISSION_DIR "%.*s.hog", l - 4, static_cast<const char *>(*altham));
char *p = althog + sizeof(MISSION_DIR) - 1;
int exists = PHYSFSX_addRelToSearchPath(p, physfs_search_path::prepend);
if (!exists) {
exists = PHYSFSX_addRelToSearchPath(p = althog, physfs_search_path::prepend);
}
bm_read_extra_robots(*altham, Mission::descent_version_type::descent2z);
if (exists)
PHYSFSX_contfile_close(p);
return 1;
}
else if (Current_mission->descent_version == Mission::descent_version_type::descent2a ||
Current_mission->descent_version == Mission::descent_version_type::descent2z ||
Current_mission->descent_version == Mission::descent_version_type::descent2x)
{
char t[50];
2020-01-18 21:57:39 +00:00
snprintf(t,sizeof(t), "%s.ham", &*Current_mission->filename);
bm_read_extra_robots(t, Current_mission->descent_version);
return 1;
} else
return 0;
}
#endif
}
2015-01-18 01:58:31 +00:00
#define tex ".tex"
static void set_briefing_filename(d_fname &f, const char *const v, std::size_t d)
{
f.copy_if(v, d);
f.copy_if(d, tex);
if (!PHYSFSX_exists(static_cast<const char *>(f), 1) && !(f.copy_if(++d, "txb"), PHYSFSX_exists(static_cast<const char *>(f), 1))) // check if this file exists ...
f = {};
}
2014-07-23 02:27:22 +00:00
static void set_briefing_filename(d_fname &f, const char *const v)
{
using std::next;
auto a = [](char c) {
return !c || c == '.';
};
auto i = std::find_if(v, next(v, f.size() - sizeof(tex)), a);
std::size_t d = std::distance(v, i);
2015-01-18 01:58:31 +00:00
set_briefing_filename(f, v, d);
2014-07-23 02:27:22 +00:00
}
2020-05-02 21:18:42 +00:00
static void record_briefing(d_fname &f, std::array<char, PATH_MAX> &buf)
2014-07-23 02:27:22 +00:00
{
const auto v = get_value(buf.data());
if (!v)
return;
const std::size_t d = std::distance(v, std::find_if(v, buf.end(), null_or_space));
if (d >= FILENAME_LEN)
return;
{
2015-01-18 01:58:31 +00:00
set_briefing_filename(f, v, std::min(d, f.size() - sizeof(tex)));
}
2014-07-23 02:27:22 +00:00
}
2015-01-18 01:58:31 +00:00
#undef tex
2014-07-23 02:27:22 +00:00
2006-03-20 17:12:09 +00:00
//loads the specfied mission from the mission list.
//build_mission_list() must have been called.
//Returns true if mission loaded ok, else false.
namespace dsx {
static const char *load_mission(const mle *const mission)
2006-03-20 17:12:09 +00:00
{
2016-05-22 17:49:31 +00:00
char *v;
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
close_extra_robot_movie();
#endif
2020-05-02 21:18:42 +00:00
Current_mission = std::make_unique<Mission>(static_cast<const Mission_path &>(*mission));
Current_mission->builtin_hogsize = mission->builtin_hogsize;
Current_mission->mission_name.copy_if(mission->mission_name);
#if defined(DXX_BUILD_DESCENT_II)
Current_mission->descent_version = mission->descent_version;
#endif
Current_mission->anarchy_only_flag = mission->anarchy_only_flag;
Current_mission->n_secret_levels = 0;
#if defined(DXX_BUILD_DESCENT_II)
Current_mission->alternate_ham_file = NULL;
#endif
//init vars
Last_level = 0;
Last_secret_level = 0;
2014-07-23 02:27:22 +00:00
Briefing_text_filename = {};
Ending_text_filename = {};
2014-07-24 02:30:18 +00:00
Secret_level_table.reset();
2014-07-24 02:39:21 +00:00
Level_names.reset();
2014-07-24 02:35:57 +00:00
Secret_level_names.reset();
// for Descent 1 missions, load descent.hog
#if defined(DXX_BUILD_DESCENT_II)
if (EMULATING_D1)
#endif
{
if (!PHYSFSX_addRelToSearchPath("descent.hog", physfs_search_path::prepend))
#if defined(DXX_BUILD_DESCENT_I)
Error("descent.hog not available!\n");
#elif defined(DXX_BUILD_DESCENT_II)
Warning("descent.hog not available, this mission may be missing some files required for briefings and exit sequence\n");
#endif
2020-01-18 21:57:39 +00:00
if (!d_stricmp(Current_mission->path.c_str(), D1_MISSION_FILENAME))
return load_mission_d1();
}
#if defined(DXX_BUILD_DESCENT_II)
else
PHYSFSX_contfile_close("descent.hog");
#endif
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
if (PLAYING_BUILTIN_MISSION) {
2006-03-20 17:12:09 +00:00
switch (Current_mission->builtin_hogsize) {
case SHAREWARE_MISSION_HOGSIZE:
case MAC_SHARE_MISSION_HOGSIZE:
Briefing_text_filename = "brief2.txb";
2014-07-23 02:27:22 +00:00
Ending_text_filename = BIMD2_ENDING_FILE_SHARE;
2006-03-20 17:12:09 +00:00
return load_mission_shareware();
case OEM_MISSION_HOGSIZE:
Briefing_text_filename = "brief2o.txb";
2014-07-23 02:27:22 +00:00
Ending_text_filename = BIMD2_ENDING_FILE_OEM;
2006-03-20 17:12:09 +00:00
return load_mission_oem();
default:
Int3();
DXX_BOOST_FALLTHROUGH;
2006-03-20 17:12:09 +00:00
case FULL_MISSION_HOGSIZE:
case FULL_10_MISSION_HOGSIZE:
case MAC_FULL_MISSION_HOGSIZE:
Briefing_text_filename = "robot.txb";
2006-03-20 17:12:09 +00:00
// continue on... (use d2.mn2 from hogfile)
break;
}
}
#endif
2006-03-20 17:12:09 +00:00
//read mission from file
2014-08-16 03:59:14 +00:00
auto &msn_extension =
#if defined(DXX_BUILD_DESCENT_II)
(mission->descent_version != Mission::descent_version_type::descent1) ? MISSION_EXTENSION_DESCENT_II :
#endif
2015-10-11 22:21:00 +00:00
MISSION_EXTENSION_DESCENT_I;
2020-05-02 21:18:42 +00:00
std::array<char, PATH_MAX> mission_filename;
2016-05-22 17:49:31 +00:00
snprintf(mission_filename.data(), mission_filename.size(), "%s%s", mission->path.c_str(), msn_extension);
2006-03-20 17:12:09 +00:00
2016-05-22 17:49:31 +00:00
PHYSFSEXT_locateCorrectCase(mission_filename.data());
2006-03-20 17:12:09 +00:00
2016-05-22 17:49:31 +00:00
auto &&mfile = PHYSFSX_openReadBuffered(mission_filename.data());
if (!mfile) {
2014-07-20 22:13:25 +00:00
Current_mission.reset();
con_printf(CON_NORMAL, DXX_STRINGIZE_FL(__FILE__, __LINE__, "error: failed to open mission \"%s\""), mission_filename.data());
return "Failed to open mission file"; //error!
2006-03-20 17:12:09 +00:00
}
//for non-builtin missions, load HOG
#if defined(DXX_BUILD_DESCENT_II)
Current_mission->descent_version = mission->descent_version;
if (!PLAYING_BUILTIN_MISSION)
#endif
{
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
strcpy(&mission_filename[mission->path.size() + 1], "hog"); //change extension
PHYSFSX_addRelToSearchPath(mission_filename.data(), physfs_search_path::prepend);
2020-01-18 21:57:39 +00:00
set_briefing_filename(Briefing_text_filename, &*Current_mission->filename);
2014-07-23 02:27:22 +00:00
Ending_text_filename = Briefing_text_filename;
}
2006-03-20 17:12:09 +00:00
2014-10-19 17:17:54 +00:00
for (PHYSFSX_gets_line_t<PATH_MAX> buf; PHYSFSX_fgets(buf,mfile);)
2014-09-07 19:48:10 +00:00
{
if (istok(buf,"type"))
2006-03-20 17:12:09 +00:00
continue; //already have name, go to next line
else if (istok(buf,"briefing")) {
2014-07-23 02:27:22 +00:00
record_briefing(Briefing_text_filename, buf);
2006-03-20 17:12:09 +00:00
}
else if (istok(buf,"ending")) {
2014-07-23 02:27:22 +00:00
record_briefing(Ending_text_filename, buf);
2006-03-20 17:12:09 +00:00
}
else if (istok(buf,"num_levels")) {
if ((v=get_value(buf))!=NULL) {
char *ip;
const auto n_levels = strtoul(v, &ip, 10);
Assert(n_levels <= MAX_LEVELS_PER_MISSION);
if (n_levels > MAX_LEVELS_PER_MISSION)
continue;
if (*ip)
{
while (isspace(static_cast<unsigned>(*ip)))
++ip;
if (*ip && *ip != ';')
continue;
}
2020-05-02 21:18:42 +00:00
Level_names = std::make_unique<d_fname[]>(n_levels);
range_for (auto &i, unchecked_partial_range(Level_names.get(), n_levels))
{
2015-01-12 00:26:03 +00:00
if (!PHYSFSX_fgets(buf, mfile))
break;
auto &line = buf.line();
auto s = std::find_if(line.begin(), line.end(), null_or_space);
if (i.copy_if(buf.line(), std::distance(line.begin(), s)))
{
2006-03-20 17:12:09 +00:00
Last_level++;
}
else
break;
}
}
}
else if (istok(buf,"num_secrets")) {
if ((v=get_value(buf))!=NULL) {
char *ip;
const auto n_levels = strtoul(v, &ip, 10);
Assert(n_levels <= MAX_SECRET_LEVELS_PER_MISSION);
if (n_levels > MAX_SECRET_LEVELS_PER_MISSION)
continue;
if (*ip)
{
while (isspace(static_cast<unsigned>(*ip)))
++ip;
if (*ip && *ip != ';')
continue;
}
N_secret_levels = n_levels;
2020-05-02 21:18:42 +00:00
Secret_level_names = std::make_unique<d_fname[]>(n_levels);
Secret_level_table = std::make_unique<uint8_t[]>(n_levels);
for (int i=0;i<N_secret_levels;i++) {
if (!PHYSFSX_fgets(buf, mfile))
2006-03-20 17:12:09 +00:00
break;
const auto &line = buf.line();
const auto lb = line.begin();
/* No auto: returned value must be type const char*
* Modern glibc maintains const-ness of the input.
* Apple libc++ and mingw32 do not.
*/
const char *const t = strchr(lb, ',');
if (!t)
break;
auto a = [](char c) {
return isspace(static_cast<unsigned>(c));
};
auto s = std::find_if(lb, t, a);
if (Secret_level_names[i].copy_if(line, std::distance(lb, s)))
{
unsigned long ls = strtoul(t + 1, &ip, 10);
if (ls < 1 || ls > Last_level)
2006-03-20 17:12:09 +00:00
break;
Secret_level_table[i] = ls;
2006-03-20 17:12:09 +00:00
Last_secret_level--;
}
else
break;
}
}
}
#if defined(DXX_BUILD_DESCENT_II)
else if (Current_mission->descent_version == Mission::descent_version_type::descent2a && buf[0] == '!') {
if (istok(buf+1,"ham")) {
2020-05-02 21:18:42 +00:00
Current_mission->alternate_ham_file = std::make_unique<d_fname>();
if ((v=get_value(buf))!=NULL) {
unsigned l = strlen(v);
if (l <= 4)
2014-08-16 03:59:14 +00:00
con_printf(CON_URGENT, "Mission %s has short HAM \"%s\".", Current_mission->path.c_str(), v);
else if (l >= sizeof(*Current_mission->alternate_ham_file))
2014-08-16 03:59:14 +00:00
con_printf(CON_URGENT, "Mission %s has excessive HAM \"%s\".", Current_mission->path.c_str(), v);
else {
2014-07-23 02:27:22 +00:00
Current_mission->alternate_ham_file->copy_if(v, l + 1);
2014-08-16 03:59:14 +00:00
con_printf(CON_VERBOSE, "Mission %s will use HAM %s.", Current_mission->path.c_str(), static_cast<const char *>(*Current_mission->alternate_ham_file));
}
}
else
2014-08-16 03:59:14 +00:00
con_printf(CON_URGENT, "Mission %s has no HAM.", Current_mission->path.c_str());
}
else {
con_printf(CON_URGENT, "Mission %s uses unsupported critical directive \"%s\".", Current_mission->path.c_str(), static_cast<const char *>(buf));
Last_level = 0;
break;
}
}
#endif
2006-03-20 17:12:09 +00:00
}
mfile.reset();
2006-03-20 17:12:09 +00:00
if (Last_level <= 0) {
2014-07-20 22:13:25 +00:00
Current_mission.reset(); //no valid mission loaded
return "Failed to parse mission file";
2006-03-20 17:12:09 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
// re-read default HAM file, in case this mission brings it's own version of it
free_polygon_models(LevelSharedPolygonModelState);
if (load_mission_ham())
2020-01-18 21:57:39 +00:00
init_extra_robot_movie(&*Current_mission->filename);
#endif
return nullptr;
2006-03-20 17:12:09 +00:00
}
2006-03-20 17:12:09 +00:00
//loads the named mission if exists.
//Returns nullptr if mission loaded ok, else error string.
const char *load_mission_by_name (const mission_entry_predicate mission_name, const mission_name_type name_match_mode)
2006-03-20 17:12:09 +00:00
{
auto &&mission_list = build_mission_list(mission_filter_mode::include_anarchy);
{
range_for (auto &i, mission_list)
{
switch (name_match_mode)
{
case mission_name_type::basename:
if (!d_stricmp(mission_name.filesystem_name, &*i.filename))
return load_mission(&i);
continue;
case mission_name_type::pathname:
case mission_name_type::guess:
if (const auto r = compare_mission_by_pathname(mission_name, i))
return load_mission(r);
continue;
default:
return "Unhandled load mission type";
}
}
}
if (name_match_mode == mission_name_type::guess)
{
const auto p = strrchr(mission_name.filesystem_name, '/');
const auto &guess_predicate = p
? mission_name.with_filesystem_name(p + 1)
: mission_name;
range_for (auto &i, mission_list)
{
if (const auto r = compare_mission_by_guess(guess_predicate, i))
{
con_printf(CON_NORMAL, "%s:%u: request for guessed mission name \"%s\" found \"%s\"", __FILE__, __LINE__, mission_name.filesystem_name, r->path.c_str());
return load_mission(r);
}
}
}
return "No matching mission found in\ninstalled mission list.";
2006-03-20 17:12:09 +00:00
}
}
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
namespace {
template <typename tag>
class unique_menu_tagged_string : std::unique_ptr<char[]>
{
public:
unique_menu_tagged_string(std::unique_ptr<char[]> p) :
std::unique_ptr<char[]>(std::move(p))
{
}
using std::unique_ptr<char[]>::get;
operator menu_tagged_string<tag>() const &
{
return {get()};
}
operator menu_tagged_string<tag>() const && = delete;
};
2020-12-27 22:03:09 +00:00
struct mission_menu : listbox
{
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
static constexpr char listbox_go_up[] = "<..>";
using callback_type = window_event_result (*)(void);
2020-12-27 22:03:09 +00:00
/* The top level menu stores the mission data in a member variable.
* When this class is the base of toplevel_mission_menu, this
* reference points to that member variable in
* toplevel_mission_menu.
*
* Subdirectory menus do not store a copy of the mission data, and
* instead store a reference to the mission data in the top level
* menu. When this class is the base of subdirectory_mission_menu,
* this reference points to the member variable in the
* toplevel_mission_menu that created this subdirectory_mission_menu.
*
* The data is not reference counted, because the top level menu
* always outlives the subdirectory menus.
*/
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
const mission_list_type &ml;
2020-12-27 22:03:09 +00:00
/* listbox stores an unowned pointer to the string pointers that it
* uses. This member variable stores an owned pointer to the same
* string pointers, so that the string pointers persist for the life
* of the listbox. The strings are stored elsewhere.
*/
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
const std::unique_ptr<const char *[]> listbox_strings;
2020-12-27 22:03:09 +00:00
/* listbox stores an unowned pointer to the title string, since some
* listboxes use statically allocated strings. This member variable
* owns the storage for this listbox's title.
*/
const unique_menu_tagged_string<menu_title_tag> title;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
const callback_type when_selected;
2020-12-27 22:03:09 +00:00
virtual window_event_result callback_handler(const d_event &, window_event_result) override;
mission_menu(const mission_list_type &rml, std::unique_ptr<const char *[]> &&name_pointer_strings, const char *const message, callback_type when_selected, mission_menu *const parent, const int default_item, grs_canvas &canvas) :
mission_menu(rml, std::move(name_pointer_strings), prepare_title(message, rml), when_selected, parent, default_item, canvas)
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
{
}
2020-12-27 22:03:09 +00:00
protected:
mission_menu *parent = nullptr;
mission_menu(const mission_list_type &rml, std::unique_ptr<const char *[]> &&name_pointer_strings, unique_menu_tagged_string<menu_title_tag> title_parameter, callback_type when_selected, mission_menu *const parent, const int default_item, grs_canvas &canvas) :
listbox(default_item, rml.size(), name_pointer_strings.get(), title_parameter, canvas, 1),
ml(rml), listbox_strings(std::move(name_pointer_strings)),
title(std::move(title_parameter)),
when_selected(when_selected), parent(parent)
{
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
}
static unique_menu_tagged_string<menu_title_tag> prepare_title(const char *const message, const mission_list_type &ml)
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
{
mission_subdir_stats ss;
ss.count(ml);
2020-05-02 21:18:42 +00:00
std::array<char, 12> dirbuf;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
char buf[128];
const auto r = 1u + std::snprintf(buf, sizeof(buf), "%s\n[%sMSN:LOCAL %zu; TOTAL %zu]", message, prepare_mission_list_count_dirbuf(dirbuf, ss.immediate_directories), ss.immediate_missions, ss.total_missions);
unique_menu_tagged_string<menu_title_tag> p = std::make_unique<char[]>(r);
std::memcpy(p.get(), buf, r);
return p;
}
};
2020-12-27 22:03:09 +00:00
struct toplevel_mission_menu_storage
{
protected:
const mission_list_type mission_list_storage;
toplevel_mission_menu_storage(mission_list_type &&rml) :
mission_list_storage(std::move(rml))
{
}
};
struct toplevel_mission_menu : toplevel_mission_menu_storage, mission_menu
{
public:
toplevel_mission_menu(mission_list_type &&rml, std::unique_ptr<const char *[]> &&name_pointer_strings, const char *const message, callback_type when_selected, const int default_item, grs_canvas &canvas) :
toplevel_mission_menu_storage(std::move(rml)),
mission_menu(mission_list_storage, std::move(name_pointer_strings), message, when_selected, nullptr /* no parent for toplevel menu */, default_item, canvas)
{
}
};
struct subdirectory_mission_menu : mission_menu
{
using mission_menu::mission_menu;
};
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
constexpr char mission_menu::listbox_go_up[];
struct mission_menu_create_state
{
std::unique_ptr<const char *[]> listbox_strings;
unsigned initial_selection = UINT_MAX;
std::unique_ptr<mission_menu_create_state> submenu;
mission_menu_create_state(const std::size_t len) :
2020-05-02 21:18:42 +00:00
listbox_strings(std::make_unique<const char *[]>(len))
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
{
}
mission_menu_create_state(mission_menu_create_state &&) = default;
};
}
2020-12-27 22:03:09 +00:00
window_event_result mission_menu::callback_handler(const d_event &event, window_event_result)
{
2014-10-04 21:47:13 +00:00
switch (event.type)
{
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
case EVENT_WINDOW_CREATED:
break;
case EVENT_NEWMENU_SELECTED:
{
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
const auto raw_citem = static_cast<const d_select_event &>(event).citem;
auto citem = raw_citem;
2020-12-27 22:03:09 +00:00
if (parent)
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
{
if (citem == 0)
{
/* Clear parent pointer so that the parent window is
* not implicitly closed during handling of
* EVENT_WINDOW_CLOSE.
*/
2020-12-27 22:03:09 +00:00
parent = nullptr;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
return window_event_result::close;
}
/* Adjust for the "Go up" placeholder item */
-- citem;
}
if (citem >= 0)
{
2020-12-27 22:03:09 +00:00
auto &mli = ml[citem];
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
if (!mli.directory.empty())
{
2020-05-02 21:18:42 +00:00
auto listbox_strings = std::make_unique<const char *[]>(mli.directory.size() + 1);
2020-12-27 22:03:09 +00:00
listbox_strings[0] = listbox_go_up;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
const auto a = [](const mle &m) -> const char * {
return m.mission_name;
};
std::transform(mli.directory.begin(), mli.directory.end(), &listbox_strings[1], a);
auto submm = window_create<subdirectory_mission_menu>(mli.directory, std::move(listbox_strings), mli.path.c_str(), when_selected, this, 0, grd_curscreen->sc_canvas);
2020-12-27 22:03:09 +00:00
(void)submm;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
return window_event_result::handled;
}
// Chose a mission
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
else if (const auto errstr = load_mission(&mli))
{
nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s\n\n%s\n\n%s", TXT_MISSION_ERROR, errstr, mli.path.c_str());
return window_event_result::handled; // stay in listbox so user can select another one
}
2020-12-27 22:03:09 +00:00
CGameCfg.LastMission.copy_if(listbox_strings[raw_citem]);
}
2020-12-27 22:03:09 +00:00
return (*when_selected)();
}
case EVENT_WINDOW_CLOSE:
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
/* If the user dismisses the listbox by pressing ESCAPE,
* do not close the parent listbox.
*/
2020-12-27 22:03:09 +00:00
if (listbox_get_citem(*this) != -1)
if (parent)
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
{
2020-12-27 22:03:09 +00:00
window_close(parent);
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
}
break;
default:
break;
}
return window_event_result::ignored;
}
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
using mission_menu_create_state_ptr = std::unique_ptr<mission_menu_create_state>;
static mission_menu_create_state_ptr prepare_mission_menu_state(const mission_list_type &mission_list, const char *const LastMission, const std::size_t extra_strings)
{
auto mission_name_to_select = LastMission;
2020-05-02 21:18:42 +00:00
auto p = std::make_unique<mission_menu_create_state>(mission_list.size() + extra_strings);
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
auto &create_state = *p.get();
auto listbox_strings = create_state.listbox_strings.get();
std::fill_n(listbox_strings, extra_strings, nullptr);
listbox_strings += extra_strings;
range_for (auto &&e, enumerate(mission_list))
{
auto &mli = e.value;
const char *const mission_name = mli.mission_name;
*listbox_strings++ = mission_name;
if (!mission_name_to_select)
continue;
if (!mli.directory.empty())
{
auto &&substate = prepare_mission_menu_state(mli.directory, mission_name_to_select, 1);
if (substate->initial_selection == UINT_MAX)
continue;
substate->listbox_strings[0] = mission_menu::listbox_go_up;
create_state.submenu = std::move(substate);
}
else if (strcmp(mission_name, mission_name_to_select))
continue;
create_state.initial_selection = e.idx;
mission_name_to_select = nullptr;
}
return p;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
}
namespace dsx {
int select_mission(const mission_filter_mode mission_filter, const menu_title message, window_event_result (*when_selected)(void))
2006-03-20 17:12:09 +00:00
{
auto &&mission_list = build_mission_list(mission_filter);
2006-03-20 17:12:09 +00:00
int new_mission_num;
2014-08-15 22:59:54 +00:00
if (mission_list.size() <= 1)
{
new_mission_num = !mission_list.empty() && !load_mission(&mission_list.front()) ? 0 : -1;
(*when_selected)();
return (new_mission_num >= 0);
}
else
{
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
auto &&create_state_ptr = prepare_mission_menu_state(mission_list, CGameCfg.LastMission, 0);
auto &create_state = *create_state_ptr.get();
mission_menu *parent_mission_menu;
{
2020-12-27 22:03:09 +00:00
auto mm = window_create<toplevel_mission_menu>(std::move(mission_list), std::move(create_state.listbox_strings), message, when_selected, create_state.initial_selection == UINT_MAX ? 0 : create_state.initial_selection, grd_curscreen->sc_canvas);
parent_mission_menu = mm;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
}
for (auto parent_state = &create_state; const auto substate = parent_state->submenu.get(); parent_state = substate)
{
const auto parent_initial_selection = parent_state->initial_selection;
const auto parent_mission_list_size = parent_mission_menu->ml.size();
assert(parent_initial_selection < parent_mission_list_size);
if (parent_initial_selection >= parent_mission_list_size)
break;
const auto &substate_mission_list = parent_mission_menu->ml[parent_initial_selection];
2020-12-27 22:03:09 +00:00
auto submm = window_create<subdirectory_mission_menu>(substate_mission_list.directory, std::move(substate->listbox_strings), substate_mission_list.path.c_str(), when_selected, parent_mission_menu, 0, grd_curscreen->sc_canvas);
parent_mission_menu = submm;
Retain directory structure in New Game dialog User jcotton42 suggested copying a D2X-XL feature: preserving the directory structure of the user's missions area when showing a New Game dialog. This was substantially more trouble than it should have been, but the result is good. Previously, the dialog presented all missions at any depth below the starting point, and sorted them as if they were all in the root directory. Now: - Empty directories are hidden entirely. There is nothing for the user to do in them, so there is no point showing them. - A directory with exactly one entry has that entry promoted into the parent, since there is no ambiguity about what the user would want. If the parent in turn has only that one promoted element when the scan of the parent finishes, then the element can be promoted up again. This continues until the root is reached or until a level has more than one entry. For this purpose, both missions and directories count as entries. - Directory entries are decorated to inform the user how many immediate subdirectories are present, how many missions are present immediately in the directory, and how many missions total are present, counting all subdirectories. If there are zero immediate subdirectories, then the directory count is not shown. For this purpose, directories that were hidden due to a lack of missions are not counted. - Sub-dialog boxes for inner directories use a title that reminds the user of the path so far, and recaps the directory/mission statistics. - On entry to the New Game dialog, if the last played mission is in a sub-dialog, appropriate sub-dialogs are opened so that the last played mission can be pre-selected. Currently, there is no in-game override to return to the prior rollup rules. Requested-by: jcotton42 <https://github.com/dxx-rebirth/dxx-rebirth/issues/392>
2018-07-03 05:59:40 +00:00
}
2006-03-20 17:12:09 +00:00
}
return 1; // presume success
2006-03-20 17:12:09 +00:00
}
#if DXX_USE_EDITOR
static int write_mission(void)
{
auto &msn_extension =
#if defined(DXX_BUILD_DESCENT_II)
(Current_mission->descent_version != Mission::descent_version_type::descent1) ? MISSION_EXTENSION_DESCENT_II :
#endif
MISSION_EXTENSION_DESCENT_I;
2020-05-02 21:18:42 +00:00
std::array<char, PATH_MAX> mission_filename;
snprintf(mission_filename.data(), mission_filename.size(), "%s%s", Current_mission->path.c_str(), msn_extension);
auto &&mfile = PHYSFSX_openWriteBuffered(mission_filename.data());
if (!mfile)
{
PHYSFS_mkdir(MISSION_DIR); //try making directory - in *write* path
mfile = PHYSFSX_openWriteBuffered(mission_filename.data());
if (!mfile)
return 0;
}
const char *prefix = "";
#if defined(DXX_BUILD_DESCENT_II)
switch (Current_mission->descent_version)
{
case Mission::descent_version_type::descent2x:
prefix = "x";
break;
case Mission::descent_version_type::descent2z:
prefix = "z";
break;
case Mission::descent_version_type::descent2a:
prefix = "!";
break;
default:
break;
}
#endif
PHYSFSX_printf(mfile, "%sname = %s\n", prefix, static_cast<const char *>(Current_mission->mission_name));
PHYSFSX_printf(mfile, "type = %s\n", Current_mission->anarchy_only_flag ? "anarchy" : "normal");
if (Briefing_text_filename[0])
PHYSFSX_printf(mfile, "briefing = %s\n", static_cast<const char *>(Briefing_text_filename));
if (Ending_text_filename[0])
PHYSFSX_printf(mfile, "ending = %s\n", static_cast<const char *>(Ending_text_filename));
PHYSFSX_printf(mfile, "num_levels = %i\n", Last_level);
range_for (auto &i, unchecked_partial_range(Level_names.get(), Last_level))
PHYSFSX_printf(mfile, "%s\n", static_cast<const char *>(i));
if (N_secret_levels)
{
PHYSFSX_printf(mfile, "num_secrets = %i\n", N_secret_levels);
for (int i = 0; i < N_secret_levels; i++)
PHYSFSX_printf(mfile, "%s,%i\n", static_cast<const char *>(Secret_level_names[i]), Secret_level_table[i]);
}
#if defined(DXX_BUILD_DESCENT_II)
if (Current_mission->alternate_ham_file)
PHYSFSX_printf(mfile, "ham = %s\n", static_cast<const char *>(*Current_mission->alternate_ham_file.get()));
#endif
return 1;
}
void create_new_mission(void)
{
2020-05-02 21:18:42 +00:00
Current_mission = std::make_unique<Mission>(Mission_path(MISSION_DIR "new_miss", sizeof(MISSION_DIR) - 1)); // limited to eight characters because of savegame format
Current_mission->mission_name.copy_if("Untitled");
Current_mission->builtin_hogsize = 0;
Current_mission->anarchy_only_flag = 0;
2020-05-02 21:18:42 +00:00
Level_names = std::make_unique<d_fname[]>(1);
if (!Level_names)
{
2014-07-20 22:13:25 +00:00
Current_mission.reset();
return;
}
2014-07-23 02:27:22 +00:00
Level_names[0] = "GAMESAVE.LVL";
Last_level = 1;
N_secret_levels = 0;
Last_secret_level = 0;
Briefing_text_filename = {};
Ending_text_filename = {};
Secret_level_table.reset();
Secret_level_names.reset();
#if defined(DXX_BUILD_DESCENT_II)
if (Gamesave_current_version > 3)
Current_mission->descent_version = Mission::descent_version_type::descent2; // custom ham not supported in editor (yet)
else
Current_mission->descent_version = Mission::descent_version_type::descent1;
Current_mission->alternate_ham_file = nullptr;
#endif
write_mission();
}
#endif
}