Skip to content

Commit 858441b

Browse files
committed
add series wad creation
creates a wad from embedded textures in connected maps
1 parent eb2d6f4 commit 858441b

File tree

4 files changed

+197
-27
lines changed

4 files changed

+197
-27
lines changed

src/bsp/Bsp.cpp

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2816,6 +2816,10 @@ float Bsp::calc_allocblock_usage() {
28162816
if (info.nFlags & TEX_SPECIAL)
28172817
continue; // does not use lightmaps
28182818

2819+
BSPMIPTEX* tex = get_texture(info.iMiptex);
2820+
if (tex && tex->szName[0] == '!')
2821+
continue; // water doesn't use lightmaps
2822+
28192823
int size[2];
28202824
GetFaceLightmapSize(this, i, size);
28212825

@@ -3785,6 +3789,43 @@ void Bsp::remove_unused_wads(vector<Wad*>& wads) {
37853789
}
37863790
}
37873791

3792+
// returns data for all embedded textures, ready to be wrtten to a WAD
3793+
vector<WADTEX> Bsp::get_embedded_textures() {
3794+
vector<WADTEX> wadTextures;
3795+
3796+
for (int i = 0; i < textureCount; i++) {
3797+
int32_t offset = ((int32_t*)textures)[i + 1];
3798+
BSPMIPTEX* tex = (BSPMIPTEX*)(textures + offset);
3799+
3800+
if (tex->nOffsets[0] == 0) {
3801+
continue; // not embedded
3802+
}
3803+
3804+
WADTEX copy;
3805+
memcpy(&copy, tex, sizeof(BSPMIPTEX)); // copy name, offset, dimenions
3806+
int dataSz = copy.getDataSize();
3807+
copy.data = new byte[dataSz];
3808+
memcpy(copy.data, (byte*)tex + tex->nOffsets[0], dataSz);
3809+
3810+
wadTextures.push_back(copy);
3811+
}
3812+
3813+
return wadTextures;
3814+
}
3815+
3816+
int Bsp::get_texture_id(string name) {
3817+
for (int i = 0; i < textureCount; i++) {
3818+
int32_t offset = ((int32_t*)textures)[i + 1];
3819+
BSPMIPTEX* tex = (BSPMIPTEX*)(textures + offset);
3820+
3821+
if (!strcasecmp(tex->szName, name.c_str())) {
3822+
return i;
3823+
}
3824+
}
3825+
3826+
return -1;
3827+
}
3828+
37883829
int Bsp::zero_entity_origins(string classname) {
37893830
int moveCount = 0;
37903831

@@ -3902,7 +3943,7 @@ bool Bsp::embed_texture(int textureId, vector<Wad*>& wads) {
39023943
return embedded;
39033944
}
39043945

3905-
int Bsp::unembed_texture(int textureId, vector<Wad*>& wads, bool force) {
3946+
int Bsp::unembed_texture(int textureId, vector<Wad*>& wads, bool force, bool quiet) {
39063947
int32_t texOffset = ((int32_t*)textures)[textureId + 1];
39073948

39083949
BSPMIPTEX* tex = get_texture(textureId);
@@ -3965,7 +4006,8 @@ int Bsp::unembed_texture(int textureId, vector<Wad*>& wads, bool force) {
39654006
memcpy(newTexData, lumps[LUMP_TEXTURES], endOffset);
39664007
memcpy(newTexData + endOffset, lumps[LUMP_TEXTURES] + endOffset + texDataSz, newTexBufferSz - endOffset);
39674008

3968-
logf("Unembedded texture %s\n", tex->szName);
4009+
if (!quiet)
4010+
logf("Unembedded texture %s\n", tex->szName);
39694011
delete[] lumps[LUMP_TEXTURES];
39704012
lumps[LUMP_TEXTURES] = newTexData;
39714013
header.lump[LUMP_TEXTURES].nLength -= texDataSz;
@@ -4713,6 +4755,7 @@ void Bsp::write(string path) {
47134755
// write the lumps
47144756
for (int i = 0; i < HEADER_LUMPS; i++) {
47154757
file.write((char*)lumps[i], header.lump[i].nLength);
4758+
//logf("LUMP %10s = %.2f MB\n", g_lump_names[i], (float)header.lump[i].nLength / (1024.0f*1024.0f));
47164759
}
47174760
}
47184761

@@ -5254,6 +5297,7 @@ bool Bsp::validate() {
52545297
mark_model_structures(i, &usage, false);
52555298
usage.compute_sum();
52565299
if (usage.sum.faces != models[i].nFaces) {
5300+
//logf("Bad face count in model %d: %d / %d (fixed)\n", i, usage.sum.faces, models[i].nFaces);
52575301
logf("Bad face count in model %d: %d / %d\n", i, usage.sum.faces, models[i].nFaces);
52585302
}
52595303
}

src/bsp/Bsp.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ class Bsp
284284
bool embed_texture(int textureId, vector<Wad*>& wads);
285285

286286
// return 1 on success, 0 on failure, 2 on success and resize
287-
int unembed_texture(int textureId, vector<Wad*>& wads, bool force=false);
287+
int unembed_texture(int textureId, vector<Wad*>& wads, bool force=false, bool quiet=false);
288288

289289
// adds a texture reference to the BSP (does not embed it)
290290
// returns an iMipTex for use in texture infos
@@ -302,6 +302,12 @@ class Bsp
302302

303303
void remove_unused_wads(vector<Wad*>& wads);
304304

305+
// returns data for all embedded textures, ready to be wrtten to a WAD
306+
vector<WADTEX> get_embedded_textures();
307+
308+
// gets the ID for a texture, or -1 if not found
309+
int get_texture_id(string name);
310+
305311
// updates texture coordinates after a texture has been resized
306312
void adjust_resized_texture_coordinates(BSPFACE& face, BSPTEXTUREINFO& info, int newWidth, int newHeight, int oldWidth, int oldHeight);
307313
void adjust_resized_texture_coordinates(int textureId, int oldWidth, int oldHeight);

src/editor/Gui.cpp

Lines changed: 143 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,23 +1114,7 @@ void Gui::drawStandardMenuBar() {
11141114
1, wadFilterPatterns, "Half-Life Package (*.wad)");
11151115

11161116
if (fname) {
1117-
vector<WADTEX> wadTextures;
1118-
for (int i = 0; i < map->textureCount; i++) {
1119-
int32_t offset = ((int32_t*)map->textures)[i + 1];
1120-
BSPMIPTEX* tex = (BSPMIPTEX*)(map->textures + offset);
1121-
1122-
if (tex->nOffsets[0] == 0) {
1123-
continue; // not embedded
1124-
}
1125-
1126-
WADTEX copy;
1127-
memcpy(&copy, tex, sizeof(BSPMIPTEX)); // copy name, offset, dimenions
1128-
int dataSz = copy.getDataSize();
1129-
copy.data = new byte[dataSz];
1130-
memcpy(copy.data, (byte*)tex + tex->nOffsets[0], dataSz);
1131-
1132-
wadTextures.push_back(copy);
1133-
}
1117+
vector<WADTEX> wadTextures = map->get_embedded_textures();
11341118

11351119
Wad outWad = Wad();
11361120
outWad.write(fname, &wadTextures[0], wadTextures.size());
@@ -1839,9 +1823,18 @@ void Gui::drawStandardMenuBar() {
18391823
}
18401824
tooltip(g, "Scales up textures on invisible model faces to reduce AllocBlock size. "
18411825
"Manually increase texture scales or downscale large textures to reduce "
1842-
"AllocBlocks further.\n");
1826+
"AllocBlocks further.");
18431827

18441828
if (ImGui::BeginMenu("Textures")) {
1829+
if (ImGui::MenuItem("Create Series WAD", "")) {
1830+
createSeriesWad();
1831+
}
1832+
tooltip(g, "Creates a WAD file containing the embedded textures from all maps in a series. "
1833+
"Textures with the same name but different appearance will not be exported.\n\n"
1834+
"Textures that are exported will also be unembedded from their BSPs, and included "
1835+
"via the newly created WAD file instead. The point of this tool is to reduce disk "
1836+
"space used by campaigns that duplicate textures across many maps.");
1837+
18451838
if (ImGui::MenuItem("Embed All", 0, false, !app->isLoading)) {
18461839
LumpReplaceCommand* command = new LumpReplaceCommand("Embed Textures");
18471840

@@ -1897,12 +1890,7 @@ void Gui::drawStandardMenuBar() {
18971890
"in the worldspawn entity.\n\nIf an embedded texture cannot be found in a WAD, it will become "
18981891
"a missing texture. You may want to export embedded textures first to avoid losing data.");
18991892

1900-
if (ImGui::MenuItem("Remove Unused WADs", 0, false, !app->isLoading)) {
1901-
LumpReplaceCommand* command = new LumpReplaceCommand("Remove Unused WADs", true);
1902-
map->remove_unused_wads(wads);
1903-
command->pushUndoState();
1904-
}
1905-
tooltip(g, "Removes unused WADs from the worldspawn 'wad' keyvalue and strips folder paths.");
1893+
ImGui::Separator();
19061894

19071895
if (ImGui::MenuItem("Downscale Invalid", 0, false, !app->isLoading)) {
19081896
LumpReplaceCommand* command = new LumpReplaceCommand("Downscale Textures");
@@ -1917,6 +1905,13 @@ void Gui::drawStandardMenuBar() {
19171905
"and adjusts texture coordinates accordingly.\n\nIf a texture is stored in a WAD, "
19181906
"it is first embedded into the BSP before being downscaled.\n");
19191907

1908+
if (ImGui::MenuItem("Remove Unused WADs", 0, false, !app->isLoading)) {
1909+
LumpReplaceCommand* command = new LumpReplaceCommand("Remove Unused WADs", true);
1910+
map->remove_unused_wads(wads);
1911+
command->pushUndoState();
1912+
}
1913+
tooltip(g, "Removes unused WADs from the worldspawn 'wad' keyvalue and strips folder paths.");
1914+
19201915
ImGui::EndMenu();
19211916
}
19221917

@@ -7990,4 +7985,128 @@ void Gui::windowResized(int width, int height) {
79907985

79917986
string Gui::getUserLayoutPath() {
79927987
return getFolderPath(ImGui::GetIO().IniFilename) + "imgui_user.ini";
7988+
}
7989+
7990+
void Gui::createSeriesWad() {
7991+
char* fnamestr = tinyfd_openFileDialog("Select Series Maps", "",
7992+
1, bspFilterPatterns, "GoldSrc Map Files (*.bsp)", 1);
7993+
7994+
vector<string> fnames;
7995+
7996+
if (fnamestr)
7997+
fnames = splitString(fnamestr, "|");
7998+
7999+
string defaultPath = getFolderPath(getFolderPath(fnames[0]));
8000+
8001+
char* sharedWadName = tinyfd_saveFileDialog("Series WAD Name", defaultPath.c_str(),
8002+
1, wadFilterPatterns, "Half-Life Package (*.wad)");
8003+
8004+
if (!fnames.size() || !sharedWadName) {
8005+
return;
8006+
}
8007+
8008+
8009+
vector<WADTEX> sharedTex;
8010+
vector<Wad*> emptyWadList;
8011+
8012+
for (int i = 0; i < fnames.size(); i++) {
8013+
Bsp* temp = new Bsp(fnames[i]);
8014+
if (!temp->valid) {
8015+
delete temp;
8016+
continue;
8017+
}
8018+
8019+
vector<WADTEX> embedded = temp->get_embedded_textures();
8020+
8021+
if (embedded.empty()) {
8022+
logf("No embedded textures found in %s\n", temp->name);
8023+
delete temp;
8024+
continue;
8025+
}
8026+
8027+
int numExport = 0;
8028+
bool anyUnembed = false;
8029+
8030+
for (int j = 0; j < embedded.size(); j++) {
8031+
WADTEX& bsptex = embedded[j];
8032+
8033+
bool isConflicted = false;
8034+
bool isAlreadyWritten = false;
8035+
for (int k = 0; k < sharedTex.size(); k++) {
8036+
WADTEX& wadtex = sharedTex[k];
8037+
8038+
if (strcasecmp(wadtex.szName, bsptex.szName)) {
8039+
continue;
8040+
}
8041+
8042+
// names match, but do the contents?
8043+
if (wadtex.nHeight != bsptex.nHeight || wadtex.nWidth != bsptex.nWidth) {
8044+
logf("Texture %s in %s has different dimensions than in the WAD\n",
8045+
bsptex.szName, temp->name);
8046+
isConflicted = true;
8047+
break;
8048+
}
8049+
8050+
if (memcmp(wadtex.data, bsptex.data, bsptex.getDataSize())) {
8051+
logf("Texture %s in %s has different data than in the WAD\n",
8052+
bsptex.szName, temp->name);
8053+
isConflicted = true;
8054+
break;
8055+
}
8056+
8057+
isAlreadyWritten = true;
8058+
break;
8059+
}
8060+
8061+
if (!isConflicted) {
8062+
int id = temp->get_texture_id(bsptex.szName);
8063+
temp->unembed_texture(id, emptyWadList, true, true);
8064+
anyUnembed = true;
8065+
8066+
if (!isAlreadyWritten) {
8067+
sharedTex.push_back(bsptex);
8068+
numExport++;
8069+
}
8070+
else {
8071+
delete[] bsptex.data;
8072+
}
8073+
}
8074+
else
8075+
delete[] bsptex.data;
8076+
}
8077+
8078+
if (anyUnembed) {
8079+
bool didUpdate = false;
8080+
for (Entity* ent : temp->ents) {
8081+
if (ent->getClassname() == "worldspawn") {
8082+
string wadlist = ent->getKeyvalue("wad");
8083+
if (wadlist.size() && wadlist[wadlist.size() - 1] != ';') {
8084+
wadlist += ";";
8085+
}
8086+
wadlist += basename(sharedWadName);
8087+
ent->setOrAddKeyvalue("wad", wadlist);
8088+
didUpdate = true;
8089+
break;
8090+
}
8091+
}
8092+
if (!didUpdate) {
8093+
logf("ERROR: %s does not have a worldspawn entity to update.\n", temp->name);
8094+
}
8095+
temp->update_ent_lump();
8096+
}
8097+
8098+
logf("Added %d / %d embedded textures from %s\n",
8099+
numExport, embedded.size(), temp->name.c_str());
8100+
8101+
temp->write(fnames[i]);
8102+
delete temp;
8103+
}
8104+
8105+
Wad outWad = Wad();
8106+
outWad.write(sharedWadName, &sharedTex[0], sharedTex.size());
8107+
logf("Exported %d embedded textures to %s\n", sharedTex.size(), sharedWadName);
8108+
8109+
for (WADTEX& tex : sharedTex) {
8110+
delete[] tex.data;
8111+
}
79938112
}

src/editor/Gui.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,5 @@ class Gui {
180180
void loadFonts();
181181
void checkFaceErrors();
182182
string getUserLayoutPath(); // path to user's saved widget layout
183+
void createSeriesWad();
183184
};

0 commit comments

Comments
 (0)