qbsp: "-add additional.map" feature

This commit is contained in:
Eric Wasylishen 2022-08-01 19:09:37 -06:00
parent 52dff47a86
commit e05a2bdf75
6 changed files with 120 additions and 1 deletions

View File

@ -688,7 +688,7 @@ public:
return create_solid_contents();
}
void init_filesystem(const fs::path &, const settings::common_settings &options) const override
void init_filesystem(const fs::path &map_or_bsp, const settings::common_settings &options) const override
{
// Q1-like games don't care about the local
// filesystem.
@ -699,6 +699,13 @@ public:
fs::addArchive(path, true);
}
// certain features like '-add additional.map' search relative to the map we're compiling
// so add the map directory to the search path
auto map_or_bsp_dir = map_or_bsp.parent_path();
if (!map_or_bsp_dir.empty()) {
fs::addArchive(map_or_bsp_dir);
}
img::init_palette(this);
}

View File

@ -352,6 +352,7 @@ public:
// since the max world size in Q3 is {-65536, -65536, -65536, 65536, 65536, 65536}. should we dynamically change this?
// should we automatically turn this on if the world gets too big but leave it off for smaller worlds?
setting_blocksize blocksize{this, "blocksize", { 0, 0, 0 }, &common_format_group, "from q3map2; split the world by x/y/z sized chunks, speeding up split decisions"};
setting_string add{this, "add", "", "", &common_format_group, "the given map file will be appended to the base map"};
void setParameters(int argc, const char **argv) override
{

View File

@ -2051,6 +2051,36 @@ void LoadMapFile(void)
map.entities.pop_back();
}
// -add function
if (!qbsp_options.add.value().empty()) {
auto file = fs::load(qbsp_options.add.value());
if (!file) {
FError("Couldn't load map file \"{}\".\n", qbsp_options.add.value());
return;
}
parser_t parser(file->data(), file->size());
for (int i = 0;; i++) {
mapentity_t &entity = map.entities.emplace_back();
if (!ParseEntity(parser, &entity)) {
break;
}
if (entity.epairs.get("classname") == "worldspawn") {
// The easiest way to get the additional map's worldspawn brushes
// into the base map's is to rename the additional map's worldspawn classname to func_group
entity.epairs.set("classname", "func_group");
}
}
// Remove dummy entity inserted above
assert(!map.entities.back().epairs.size());
assert(map.entities.back().brushes.empty());
map.entities.pop_back();
}
logging::print(logging::flag::STAT, " {:8} faces\n", map.faces.size());
logging::print(logging::flag::STAT, " {:8} brushes\n", map.brushes.size());
logging::print(logging::flag::STAT, " {:8} entities\n", map.entities.size());

View File

@ -0,0 +1,31 @@
// Game: Quake
// Format: Valve
// entity 0
{
"mapversion" "220"
"classname" "worldspawn"
"message" "merge maps addition. The worldspawn keys here should be ignored"
"wad" "deprecated/free_wad.wad"
// brush 0
{
( 0 -64 -16 ) ( 0 -63 -16 ) ( 0 -64 -15 ) bolt11 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 0 -64 -16 ) ( 0 -64 -15 ) ( 1 -64 -16 ) bolt11 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 0 -64 -16 ) ( 1 -64 -16 ) ( 0 -63 -16 ) bolt11 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 128 64 16 ) ( 128 65 16 ) ( 129 64 16 ) bolt11 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 128 64 16 ) ( 129 64 16 ) ( 128 64 17 ) bolt11 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 64 64 16 ) ( 64 64 17 ) ( 64 65 16 ) bolt11 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
}
// entity 1
{
"classname" "func_wall"
// brush 0
{
( 96 32 0 ) ( 96 33 0 ) ( 96 32 1 ) bolt11 [ 0 -1 0 0 ] [ 0 0 -1 -16 ] 0 1 1
( 96 32 0 ) ( 96 32 1 ) ( 97 32 0 ) bolt11 [ 1 0 0 0 ] [ 0 0 -1 -16 ] 0 1 1
( 96 32 0 ) ( 97 32 0 ) ( 96 33 0 ) bolt11 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 128 64 16 ) ( 128 65 16 ) ( 129 64 16 ) bolt11 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 128 64 16 ) ( 129 64 16 ) ( 128 64 17 ) bolt11 [ -1 0 0 0 ] [ 0 0 -1 -16 ] 0 1 1
( 128 64 16 ) ( 128 64 17 ) ( 128 65 16 ) bolt11 [ 0 1 0 0 ] [ 0 0 -1 -16 ] 0 1 1
}
}

View File

@ -0,0 +1,23 @@
// Game: Quake
// Format: Valve
// entity 0
{
"mapversion" "220"
"classname" "worldspawn"
"message" "merge maps base"
"wad" "deprecated/free_wad.wad"
// brush 0
{
( -64 -64 -16 ) ( -64 -63 -16 ) ( -64 -64 -15 ) bolt11 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -64 -64 -16 ) ( -64 -64 -15 ) ( -63 -64 -16 ) bolt11 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -64 -64 -16 ) ( -63 -64 -16 ) ( -64 -63 -16 ) bolt11 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 64 64 16 ) ( 64 65 16 ) ( 65 64 16 ) bolt11 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 64 64 16 ) ( 65 64 16 ) ( 64 64 17 ) bolt11 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 0 64 16 ) ( 0 64 17 ) ( 0 65 16 ) bolt11 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
}
// entity 1
{
"classname" "info_player_start"
"origin" "-16 -16 40"
}

View File

@ -1766,6 +1766,33 @@ TEST_CASE("q1_wad_external", "[testmaps_q1]") {
CHECK(bsp.dtex.textures[3].data.size() == sizeof(dmiptex_t));
}
TEST_CASE("q1_merge_maps", "[testmaps_q1]") {
const auto [bsp, bspx, prt] = LoadTestmapQ1("q1_merge_maps_base.map", { "-add", "q1_merge_maps_addition.map" });
CHECK(GAME_QUAKE == bsp.loadversion->game->id);
// check brushwork from the two maps is merged
REQUIRE(BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], {5,0,16}, {0, 0, 1}));
REQUIRE(BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], {-5,0,16}, {0, 0, 1}));
// check that the worldspawn keys from the base map are used
auto ents = EntData_Parse(bsp.dentdata);
REQUIRE(ents.size() == 3); // worldspawn, info_player_start, func_wall
REQUIRE(ents[0].get("classname") == "worldspawn");
CHECK(ents[0].get("message") == "merge maps base");
// check info_player_start
auto it = std::find_if(ents.begin(), ents.end(),
[](const entdict_t &dict) -> bool { return dict.get("classname") == "info_player_start"; });
REQUIRE(it != ents.end());
// check func_wall entity from addition map is included
it = std::find_if(ents.begin(), ents.end(),
[](const entdict_t &dict) -> bool { return dict.get("classname") == "func_wall"; });
REQUIRE(it != ents.end());
}
TEST_CASE("winding", "[benchmark][.releaseonly]") {
ankerl::nanobench::Bench bench;