Skip to content

Commit ea6e6d3

Browse files
committed
Add the generate_placeable_blueprints tool.
1 parent 559ded6 commit ea6e6d3

3 files changed

Lines changed: 167 additions & 0 deletions

File tree

README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ The library is completely free for anyone to copy, use, and change with or witho
1717
There are some tools in the Tools subdirectory:
1818

1919
- 2da_merge allows merging a 2da into a base 2da, overwriting rows that are already present or inserting ones that are not.
20+
- generate_placeable_blueprints allows the user to generate a series of blueprints from placeables defined in 2da using a base blueprint
2021
- key_bif_extractor allows extracting all resources in a KEY from their BIFs.

Tools/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ set_target_properties(2da_merge PROPERTIES FOLDER "Tools")
55
add_executable(key_bif_extractor Tool_KeyBifExtractor.cpp)
66
target_link_libraries(key_bif_extractor FileFormats)
77
set_target_properties(key_bif_extractor PROPERTIES FOLDER "Tools")
8+
9+
add_executable(generate_placeable_blueprints Tool_GeneratePlaceableBlueprints.cpp)
10+
target_link_libraries(generate_placeable_blueprints FileFormats)
11+
set_target_properties(generate_placeable_blueprints PROPERTIES FOLDER "Tools")
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#include "FileFormats/2da.hpp"
2+
#include "FileFormats/Gff.hpp"
3+
4+
#include <set>
5+
6+
#if OS_WINDOWS
7+
#include "Windows.h"
8+
#else
9+
#include <sys/stat.h>
10+
#endif
11+
12+
namespace {
13+
14+
// Recursively make the provided directory.
15+
void RecursivelyEnsureDir(std::string const& dir)
16+
{
17+
for (std::size_t slashIndex = dir.find_first_of("\\/");
18+
slashIndex != std::string::npos;
19+
slashIndex = dir.find_first_of("\\/", slashIndex + 1))
20+
{
21+
std::string dirToMake = dir.substr(0, slashIndex);
22+
23+
#if OS_WINDOWS
24+
CreateDirectoryA(dirToMake.c_str(), NULL);
25+
#else
26+
mkdir(dirToMake.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
27+
#endif
28+
}
29+
}
30+
31+
}
32+
33+
int GeneratePlaceableBlueprints(const char* twoDAPath, const char* blueprintPath, const char* outputFolderPath, const char* labelFilter)
34+
{
35+
using namespace FileFormats;
36+
37+
TwoDA::Raw::TwoDA twoDARaw;
38+
if (!TwoDA::Raw::TwoDA::ReadFromFile(twoDAPath, &twoDARaw))
39+
{
40+
std::printf("Failed to load 2da from %s.\n", twoDAPath);
41+
return 1;
42+
}
43+
44+
Gff::Raw::Gff rawGff;
45+
if (!Gff::Raw::Gff::ReadFromFile(blueprintPath, &rawGff))
46+
{
47+
std::printf("Failed to load the GFF file from %s.\n", blueprintPath);
48+
return 1;
49+
}
50+
51+
TwoDA::Friendly::TwoDA twoDA(std::move(twoDARaw));
52+
Gff::Friendly::Gff gff(std::move(rawGff));
53+
54+
std::string blueprintPathAsStr = blueprintPath;
55+
std::size_t lastDot = blueprintPathAsStr.find_last_of('.');
56+
57+
std::string extension;
58+
if (lastDot != std::string::npos && lastDot + 1 != blueprintPathAsStr.size())
59+
{
60+
extension = blueprintPathAsStr.substr(lastDot + 1, blueprintPathAsStr.size() - lastDot - 1);
61+
}
62+
else
63+
{
64+
extension = "gff";
65+
}
66+
67+
// For each row in the 2da, we need to grab:
68+
// - RowID -> maps to Appearance in the GFF
69+
// - Label -> maps to LocName in the GFF
70+
// - ModelName > maps to the filename and also to TemplateResRef in the GFF
71+
72+
// Used to enforce based on ModelName that we only have one of the same name.
73+
std::set<std::string> uniqueFileNameSet;
74+
75+
for (const TwoDA::Friendly::TwoDARow& row : twoDA)
76+
{
77+
std::uint32_t rowId = row.RowId();
78+
std::string label = row.AsStr("Label");
79+
std::string modelName = row.AsStr("ModelName");
80+
81+
if (labelFilter && label.find(labelFilter) == std::string::npos)
82+
{
83+
std::printf("%s: Rejecting due to filter mismatch.\n", label.c_str());
84+
continue;
85+
}
86+
87+
Gff::Friendly::Type_DWORD appearance = static_cast<Gff::Friendly::Type_DWORD>(rowId);
88+
89+
Gff::Friendly::Type_CExoLocString locName;
90+
locName.m_StringRef = 0xFFFFFFFF;
91+
locName.m_TotalSize = sizeof(locName.m_StringRef) +
92+
sizeof(std::uint32_t); // string count
93+
94+
{
95+
Gff::Friendly::Type_CExoLocString::SubString substring;
96+
substring.m_StringID = 0;
97+
substring.m_String = std::move(label);
98+
99+
locName.m_TotalSize += sizeof(substring.m_StringID) +
100+
sizeof(std::uint32_t) + // string size
101+
static_cast<std::uint32_t>(substring.m_String.size());
102+
103+
locName.m_SubStrings.emplace_back(std::move(substring));
104+
}
105+
106+
if (modelName.size() > 14)
107+
{
108+
modelName = modelName.substr(0, 14);
109+
}
110+
111+
if (uniqueFileNameSet.find(modelName) != std::end(uniqueFileNameSet))
112+
{
113+
std::string modifiedModelName;
114+
std::uint8_t i = 0;
115+
116+
do
117+
{
118+
modifiedModelName = modelName + std::to_string(i++);
119+
} while (uniqueFileNameSet.find(modifiedModelName) != std::end(uniqueFileNameSet));
120+
121+
std::printf("%s: Mapped %s -> %s\n", locName.m_SubStrings[0].m_String.c_str(), modelName.c_str(), modifiedModelName.c_str());
122+
123+
modelName = modifiedModelName;
124+
}
125+
126+
uniqueFileNameSet.insert(modelName);
127+
128+
Gff::Friendly::Type_CResRef resref;
129+
resref.m_Size = static_cast<std::uint32_t>(modelName.size());
130+
std::memcpy(resref.m_String, modelName.data(), resref.m_Size);
131+
132+
gff.GetTopLevelStruct().WriteField("Appearance", appearance);
133+
gff.GetTopLevelStruct().WriteField("LocName", locName);
134+
gff.GetTopLevelStruct().WriteField("TemplateResRef", resref);
135+
136+
char pathBuffer[1024];
137+
std::sprintf(pathBuffer, "%s/%s.%s", outputFolderPath, modelName.c_str(), extension.c_str());
138+
RecursivelyEnsureDir(pathBuffer);
139+
140+
if (gff.WriteToFile(pathBuffer))
141+
{
142+
std::printf("%s: Saved to %s.\n", locName.m_SubStrings[0].m_String.c_str(), pathBuffer);
143+
}
144+
else
145+
{
146+
std::printf("%s: ERROR: Failed to save to %s.\n", locName.m_SubStrings[0].m_String.c_str(), pathBuffer);
147+
}
148+
}
149+
150+
return 0;
151+
}
152+
153+
int main(int argc, char** argv)
154+
{
155+
if (argc < 4)
156+
{
157+
std::printf("key_bif_extractor [2da_path] [blueprint_base_path] [output_path] [optional_label_filter]\n");
158+
return 1;
159+
}
160+
161+
return GeneratePlaceableBlueprints(argv[1], argv[2], argv[3], argc >= 4 ? argv[4] : nullptr);
162+
}

0 commit comments

Comments
 (0)