From 9e0ca19d715a754515de76e26bbe44194f1dd0c0 Mon Sep 17 00:00:00 2001 From: T1ti <40864460+T1ti@users.noreply.github.com> Date: Sat, 31 Aug 2024 07:27:03 +0200 Subject: [PATCH] adspartan : implement option to save liquids as MCLQ mapchunk: store pointer to the header when saving https://github.com/wowdev/noggit3/commit/2893aadcaf3e46b88a363b70bb2524607e8ead7c https://github.com/wowdev/noggit3/commit/f7e5f8396ffc7763ab2438c0bf7f8957ad0ccdf5 noggit3 : extendable array: prevent use after extend https://github.com/wowdev/noggit3/pull/91 --- src/noggit/ChunkWater.cpp | 59 ++++++++++++++++ src/noggit/ChunkWater.hpp | 3 + src/noggit/MapChunk.cpp | 51 +++++++++++--- src/noggit/MapChunk.h | 4 +- src/noggit/MapHeaders.h | 2 +- src/noggit/MapTile.cpp | 36 +++++++++- src/noggit/MapTile.h | 7 +- src/noggit/liquid_layer.cpp | 68 ++++++++++++++++++- src/noggit/liquid_layer.hpp | 10 ++- .../windows/settingsPanel/SettingsPanel.cpp | 2 + .../ui/windows/settingsPanel/SettingsPanel.ui | 28 +++++++- 11 files changed, 251 insertions(+), 19 deletions(-) diff --git a/src/noggit/ChunkWater.cpp b/src/noggit/ChunkWater.cpp index 4d941a2b..bf1ab3c0 100755 --- a/src/noggit/ChunkWater.cpp +++ b/src/noggit/ChunkWater.cpp @@ -6,6 +6,9 @@ #include #include #include +#include + +#include ChunkWater::ChunkWater(MapChunk* chunk, TileWater* water_tile, float x, float z, bool use_mclq_green_lava) : xbase(x) @@ -163,6 +166,62 @@ void ChunkWater::save(util::sExtendableArray& adt, int base_pos, int& header_pos header_pos += sizeof(MH2O_Header); } +void ChunkWater::save_mclq(util::sExtendableArray& adt, int mcnk_pos, int& current_pos) +{ + // remove empty layers + cleanup(); + update_attributes(); + + if (hasData(0)) + { + adt.Extend(sizeof(mclq) * _layers.size() + 8); + // size seems to be 0 in vanilla adts in the mclq chunk's header and set right in the mcnk header (layer_size * n_layer + 8) + SetChunkHeader(adt, current_pos, 'MCLQ', 0); + + current_pos += 8; + + // it's possible to merge layers when they don't overlap (liquids using the same vertice, but at different height) + // layer ordering seems to matter, having a lava layer then a river layer causes the lava layer to not render ingame + // sorting order seems to be dependant on the flag ordering in the mcnk's header + std::vector> mclq_layers; + + for (liquid_layer const& layer : _layers) + { + switch (layer.mclq_liquid_type()) + { + case 6: // lava + adt.GetPointer(mcnk_pos + 8)->flags.flags.lq_magma = 1; + break; + case 3: // slime + adt.GetPointer(mcnk_pos + 8)->flags.flags.lq_slime = 1; + break; + case 1: // ocean + adt.GetPointer(mcnk_pos + 8)->flags.flags.lq_ocean = 1; + break; + default: // river + adt.GetPointer(mcnk_pos + 8)->flags.flags.lq_river = 1; + break; + } + + mclq_layers.push_back({ layer.to_mclq(attributes), layer.mclq_flag_ordering() }); + } + + auto cmp = [](std::pair const& a, std::pair const& b) + { + return a.second < b.second; + }; + + // sort the layers by flag order + std::sort(mclq_layers.begin(), mclq_layers.end(), cmp); + + for (auto const& mclq_layer : mclq_layers) + { + std::memcpy(adt.GetPointer(current_pos).get(), &mclq_layer.first, sizeof(mclq)); + current_pos += sizeof(mclq); + } + } +} + void ChunkWater::autoGen(MapChunk *chunk, float factor) { diff --git a/src/noggit/ChunkWater.hpp b/src/noggit/ChunkWater.hpp index d8d413b3..e6696e1e 100755 --- a/src/noggit/ChunkWater.hpp +++ b/src/noggit/ChunkWater.hpp @@ -32,6 +32,7 @@ public: void from_mclq(std::vector& layers); void fromFile(BlizzardArchive::ClientFile& f, size_t basePos); void save(util::sExtendableArray& adt, int base_pos, int& header_pos, int& current_pos); + void save_mclq(util::sExtendableArray& adt, int mcnk_pos, int& current_pos); bool is_visible ( const float& cull_distance , const math::frustum& frustum @@ -76,6 +77,8 @@ public: float xbase, zbase; + int layer_count() const { return _layers.size(); } + private: MH2O_Attributes attributes; diff --git a/src/noggit/MapChunk.cpp b/src/noggit/MapChunk.cpp index ff5bb7a9..98e23e52 100755 --- a/src/noggit/MapChunk.cpp +++ b/src/noggit/MapChunk.cpp @@ -135,7 +135,7 @@ MapChunk::MapChunk(MapTile* maintile, BlizzardArchive::ClientFile* f, bool bigAl f->read(&tmp_chunk_header, sizeof(MapChunkHeader)); - header_flags.value = tmp_chunk_header.flags; + header_flags.value = tmp_chunk_header.flags.value; areaID = tmp_chunk_header.areaid; zbase = tmp_chunk_header.zpos; @@ -1432,7 +1432,8 @@ void MapChunk::save(util::sExtendableArray& lADTFile , int& lMCIN_Position , std::map &lTextures , std::vector &lObjectInstances - , std::vector& lModelInstances) + , std::vector& lModelInstances + , bool use_mclq_liquids) { int lID; int lMCNK_Size = 0x80; @@ -1445,9 +1446,9 @@ void MapChunk::save(util::sExtendableArray& lADTFile // lADTFile.Insert(lCurrentPosition + 8, 0x80, reinterpret_cast(&(header))); auto const lMCNK_header = lADTFile.GetPointer(lCurrentPosition + 8); - header_flags.flags.do_not_fix_alpha_map = 1; + header_flags.flags.do_not_fix_alpha_map = use_mclq_liquids ? 0 : 1; - lMCNK_header->flags = header_flags.value; + lMCNK_header->flags = header_flags; lMCNK_header->ix = px; lMCNK_header->iy = py; lMCNK_header->zpos = zbase * -1.0f + ZEROPOINT; @@ -1690,7 +1691,8 @@ void MapChunk::save(util::sExtendableArray& lADTFile // MCSH if (has_shadows()) { - header_flags.flags.has_mcsh = 1; + // header_flags.flags.has_mcsh = 1; + lMCNK_header->flags.flags.has_mcsh = 1; int lMCSH_Size = 0x200; lADTFile.Extend(8 + lMCSH_Size); @@ -1709,7 +1711,8 @@ void MapChunk::save(util::sExtendableArray& lADTFile } else { - header_flags.flags.has_mcsh = 0; + lMCNK_header->flags.flags.has_mcsh = 0; + // header_flags.flags.has_mcsh = 0; header_ptr->ofsShadow = 0; header_ptr->sizeShadow = 0; } @@ -1731,10 +1734,42 @@ void MapChunk::save(util::sExtendableArray& lADTFile lCurrentPosition += 8 + lMCAL_Size; lMCNK_Size += 8 + lMCAL_Size; - // } - //! Don't write anything MCLQ related anymore... + if (use_mclq_liquids) + { + auto liquids = liquid_chunk(); + if (liquids && liquids->layer_count() > 0) + { + int liquids_size = 8 + liquids->layer_count() * sizeof(mclq); + + lMCNK_Size += liquids_size; + + header_ptr->sizeLiquid = liquids_size; + header_ptr->ofsLiquid = lCurrentPosition - lMCNK_Position; + + // current position updated inside + liquids->save_mclq(lADTFile, lMCNK_Position, lCurrentPosition); + } + // no liquid, vanilla adt still have an empty chunk + else + { + lADTFile.Extend(8); + // size seems to be 0 in vanilla adts in the mclq chunk's header and set right in the mcnk header (layer_size * n_layer + 8) + SetChunkHeader(lADTFile, lCurrentPosition, 'MCLQ', 0); + + header_ptr->sizeLiquid = 8; + header_ptr->ofsLiquid = lCurrentPosition - lMCNK_Position; + + // When saving a tile that had MLCQ, also remove flags in MCNK + // Client sees the flag and loads random data as if it were MCLQ, which we don’t save. + // clear MCLQ liquid flags (0x4, 0x8, 0x10, 0x20) + header_ptr->flags.value &= 0xFFFFFFC3; + + lCurrentPosition += 8; + lMCNK_Size += 8; + } + } // MCSE int lMCSE_Size = sizeof(ENTRY_MCSE) * sound_emitters.size(); diff --git a/src/noggit/MapChunk.h b/src/noggit/MapChunk.h index 8eb8c9fc..f674b3bb 100755 --- a/src/noggit/MapChunk.h +++ b/src/noggit/MapChunk.h @@ -201,7 +201,9 @@ public: , int &lMCIN_Position , std::map &lTextures , std::vector &lObjectInstances - , std::vector& lModelInstances); + , std::vector& lModelInstances + , bool use_mclq_liquids + ); // fix the gaps with the chunk to the left bool fixGapLeft(const MapChunk* chunk); diff --git a/src/noggit/MapHeaders.h b/src/noggit/MapHeaders.h index 7e58ae17..1fa42dd2 100755 --- a/src/noggit/MapHeaders.h +++ b/src/noggit/MapHeaders.h @@ -121,7 +121,7 @@ struct ENTRY_MODF }; struct MapChunkHeader { - uint32_t flags = 0; + mcnk_flags flags; uint32_t ix = 0; uint32_t iy = 0; uint32_t nLayers = 0; diff --git a/src/noggit/MapTile.cpp b/src/noggit/MapTile.cpp index 16a2eab9..2f27d127 100755 --- a/src/noggit/MapTile.cpp +++ b/src/noggit/MapTile.cpp @@ -529,6 +529,24 @@ void MapTile::getVertexInternal(float x, float z, glm::vec3* v) /// --- Only saving related below this line. -------------------------- void MapTile::saveTile(World* world) +{ + // if we want to save a duplicate with mclq in a separate folder + /* + save(world, false); + + if (NoggitSettings.value("use_mclq_liquids_export", false).toBool()) + { + save(world, true); + } + */ + + QSettings settings; + bool use_mclq = settings.value("use_mclq_liquids_export", false).toBool(); + + save(world, use_mclq); +} + +void MapTile::save(World* world, bool save_using_mclq_liquids) { Log << "Saving ADT \"" << _file_key.stringRepr() << "\"." << std::endl; @@ -848,14 +866,17 @@ void MapTile::saveTile(World* world) lCurrentPosition += 8 + lMODF_Size; //MH2O - Water.saveToFile(lADTFile, lMHDR_Position, lCurrentPosition); + if (!save_using_mclq_liquids) + { + Water.saveToFile(lADTFile, lMHDR_Position, lCurrentPosition); + } // MCNK for (int y = 0; y < 16; ++y) { for (int x = 0; x < 16; ++x) { - mChunks[y][x]->save(lADTFile, lCurrentPosition, lMCIN_Position, lTextures, lObjectInstances, lModelInstances); + mChunks[y][x]->save(lADTFile, lCurrentPosition, lMCIN_Position, lTextures, lObjectInstances, lModelInstances, save_using_mclq_liquids); } } @@ -908,6 +929,17 @@ void MapTile::saveTile(World* world) // begin with. f.setBuffer(lADTFile.data_up_to(lCurrentPosition)); // cleaning unused nulls at the end of file f.save(); + + // adspartan's way, save MCLQ files separately + /* + if (save_using_mclq_liquids) + { + f.save_file_to_folder(NoggitSettings.value("project/mclq_liquids_path").toString().toStdString()); + } + else + { + f.save(); + }*/ } lObjectInstances.clear(); diff --git a/src/noggit/MapTile.h b/src/noggit/MapTile.h index bbf9ade3..058a2b82 100755 --- a/src/noggit/MapTile.h +++ b/src/noggit/MapTile.h @@ -107,8 +107,13 @@ public: bool GetVertex(float x, float z, glm::vec3 *V); void getVertexInternal(float x, float z, glm::vec3* v); - void saveTile(World*); void CropWater(); + void saveTile(World* world); + +private: + void save(World* world, bool save_using_mclq_liquids); + +public: bool isTile(int pX, int pZ); diff --git a/src/noggit/liquid_layer.cpp b/src/noggit/liquid_layer.cpp index efba81e8..8f661816 100755 --- a/src/noggit/liquid_layer.cpp +++ b/src/noggit/liquid_layer.cpp @@ -174,7 +174,7 @@ liquid_layer::liquid_layer(ChunkWater* chunk update_min_max(); } -liquid_layer::liquid_layer(liquid_layer&& other) +liquid_layer::liquid_layer(liquid_layer&& other) noexcept : _liquid_id(other._liquid_id) , _liquid_vertex_format(other._liquid_vertex_format) , _minimum(other._minimum) @@ -207,7 +207,7 @@ liquid_layer::liquid_layer(liquid_layer const& other) changeLiquidID(_liquid_id); } -liquid_layer& liquid_layer::operator= (liquid_layer&& other) +liquid_layer& liquid_layer::operator= (liquid_layer&& other) noexcept { std::swap(_liquid_id, other._liquid_id); std::swap(_liquid_vertex_format, other._liquid_vertex_format); @@ -395,14 +395,20 @@ void liquid_layer::changeLiquidID(int id) switch (_liquid_type) { case liquid_basic_types_magma: + _mclq_liquid_type = 6; + _liquid_vertex_format = 1; + break; case liquid_basic_types_slime: + _mclq_liquid_type = 3; _liquid_vertex_format = HEIGHT_UV; break; case liquid_basic_types_ocean: // ocean _liquid_vertex_format = DEPTH; + _mclq_liquid_type = 1; break; - default: + default: // river _liquid_vertex_format = HEIGHT_DEPTH; + _mclq_liquid_type = 4; break; } } @@ -650,6 +656,62 @@ bool liquid_layer::check_fatigue() const return true; } +mclq liquid_layer::to_mclq(MH2O_Attributes& attributes) const +{ + mclq mclq_data; + + mclq_data.min_height = _minimum; + mclq_data.max_height = _maximum; + + for (int i = 0; i < 8 * 8; ++i) + { + if (hasSubchunk(i % 8, i / 8)) + { + mclq_data.tiles[i].liquid_type = _mclq_liquid_type & 0x7; + mclq_data.tiles[i].dont_render = 0; + mclq_data.tiles[i].fishable = (attributes.fishable >> i) & 1; + mclq_data.tiles[i].fatigue = (attributes.fatigue >> i) & 1; + } + else + { + mclq_data.tiles[i].liquid_type = 7; + mclq_data.tiles[i].dont_render = 1; + mclq_data.tiles[i].fishable = 0; + mclq_data.tiles[i].fatigue = 0; + } + } + + for (int i = 0; i < 9 * 9; ++i) + { + mclq_data.vertices[i].height = _vertices[i].position.y; + + // magma and slime + if (_liquid_type == 2 || _liquid_type == 3) + { + mclq_data.vertices[i].magma.x = static_cast(std::min(_vertices[i].uv.x * 255.f, 65535.f)); + mclq_data.vertices[i].magma.y = static_cast(std::min(_vertices[i].uv.y * 255.f, 65535.f)); + } + else + { + mclq_data.vertices[i].water.depth = static_cast(std::clamp(_vertices[i].depth * 255.f, 0.f, 255.f)); + } + } + + return mclq_data; +} + +int liquid_layer::mclq_flag_ordering() const +{ + switch (_mclq_liquid_type) + { + case 6: return 2; // lava + case 3: return 3; // slime + case 1: return 1; // ocean + default: return 0; // river + + } +} + void liquid_layer::update_attributes(MH2O_Attributes& attributes) { if (check_fatigue()) diff --git a/src/noggit/liquid_layer.hpp b/src/noggit/liquid_layer.hpp index e6bff468..a02b8da7 100755 --- a/src/noggit/liquid_layer.hpp +++ b/src/noggit/liquid_layer.hpp @@ -54,12 +54,13 @@ public: liquid_layer(ChunkWater* chunk, BlizzardArchive::ClientFile& f, std::size_t base_pos, glm::vec3 const& base, MH2O_Information const& info, std::uint64_t infomask); liquid_layer(liquid_layer const& other); - liquid_layer(liquid_layer&&); + liquid_layer(liquid_layer&&) noexcept; - liquid_layer& operator=(liquid_layer&&); + liquid_layer& operator=(liquid_layer&&) noexcept; liquid_layer& operator=(liquid_layer const& other); void save(util::sExtendableArray& adt, int base_pos, int& info_pos, int& current_pos) const; + mclq to_mclq(MH2O_Attributes& attributes) const; void update_attributes(MH2O_Attributes& attributes); void changeLiquidID(int id); @@ -75,6 +76,9 @@ public: float min() const { return _minimum; } float max() const { return _maximum; } int liquidID() const { return _liquid_id; } + int mclq_liquid_type() const { return _mclq_liquid_type; } + // order of the flag corresponding to the liquid type in the mcnk header + int mclq_flag_ordering() const; // used for fatigue calculation bool subchunk_at_max_depth(int x, int z) const; @@ -105,6 +109,7 @@ public: ChunkWater* getChunk() { return _chunk; }; bool has_fatigue() const { return _fatigue_enabled; } + private: void create_vertices(float height); @@ -120,6 +125,7 @@ private: int _liquid_id; int _liquid_type; int _liquid_vertex_format; + int _mclq_liquid_type; float _minimum; float _maximum; diff --git a/src/noggit/ui/windows/settingsPanel/SettingsPanel.cpp b/src/noggit/ui/windows/settingsPanel/SettingsPanel.cpp index 644b199d..2ae2ef66 100755 --- a/src/noggit/ui/windows/settingsPanel/SettingsPanel.cpp +++ b/src/noggit/ui/windows/settingsPanel/SettingsPanel.cpp @@ -219,6 +219,7 @@ namespace Noggit ui->_additional_file_loading_log->setChecked( _settings->value("additional_file_loading_log", false).toBool()); ui->_keyboard_locale->setCurrentText(_settings->value("keyboard_locale", "QWERTY").toString()); + ui->_use_mclq_liquids_export->setChecked(_settings->value("use_mclq_liquids_export", false).toBool()); ui->_theme->setCurrentText(_settings->value("theme", "Dark").toString()); ui->assetBrowserBgCol->setColor(_settings->value("assetBrowser/background_color", @@ -305,6 +306,7 @@ namespace Noggit _settings->setValue("systemWindowFrame", ui->_systemWindowFrame->isChecked()); _settings->setValue("nativeMenubar", ui->_nativeMenubar->isChecked()); _settings->setValue("classicUI", ui->_classic_ui->isChecked()); + _settings->setValue("use_mclq_liquids_export", ui->_use_mclq_liquids_export->isChecked()); #ifdef USE_MYSQL_UID_STORAGE _settings->setValue ("project/mysql/enabled", ui->MySQL_box->isChecked()); diff --git a/src/noggit/ui/windows/settingsPanel/SettingsPanel.ui b/src/noggit/ui/windows/settingsPanel/SettingsPanel.ui index 0e739b4e..b4db2ca1 100755 --- a/src/noggit/ui/windows/settingsPanel/SettingsPanel.ui +++ b/src/noggit/ui/windows/settingsPanel/SettingsPanel.ui @@ -478,7 +478,7 @@ 64 - 1 + 2 @@ -585,6 +585,15 @@ Qt::AlignCenter + + 9 + + + 9 + + + 9 + @@ -768,6 +777,23 @@ + + + + true + + + + + + + + + + Use MCLQ Liquids (vanilla/BC) export + + +