@@ -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 (©, 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\n If 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\n If 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
79917986string 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}
0 commit comments