adspartan : implement option to save liquids as MCLQ

mapchunk: store pointer to the header when saving
2893aadcaf
f7e5f8396f
noggit3 : extendable array: prevent use after extend https://github.com/wowdev/noggit3/pull/91
This commit is contained in:
T1ti
2024-08-31 07:27:03 +02:00
parent 61b607bb5c
commit 9e0ca19d71
11 changed files with 251 additions and 19 deletions

View File

@@ -6,6 +6,9 @@
#include <noggit/MapChunk.h> #include <noggit/MapChunk.h>
#include <noggit/Misc.h> #include <noggit/Misc.h>
#include <ClientFile.hpp> #include <ClientFile.hpp>
#include <util/sExtendableArray.hpp>
#include <algorithm>
ChunkWater::ChunkWater(MapChunk* chunk, TileWater* water_tile, float x, float z, bool use_mclq_green_lava) ChunkWater::ChunkWater(MapChunk* chunk, TileWater* water_tile, float x, float z, bool use_mclq_green_lava)
: xbase(x) : xbase(x)
@@ -163,6 +166,62 @@ void ChunkWater::save(util::sExtendableArray& adt, int base_pos, int& header_pos
header_pos += sizeof(MH2O_Header); 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<std::pair<mclq, int>> mclq_layers;
for (liquid_layer const& layer : _layers)
{
switch (layer.mclq_liquid_type())
{
case 6: // lava
adt.GetPointer<MapChunkHeader>(mcnk_pos + 8)->flags.flags.lq_magma = 1;
break;
case 3: // slime
adt.GetPointer<MapChunkHeader>(mcnk_pos + 8)->flags.flags.lq_slime = 1;
break;
case 1: // ocean
adt.GetPointer<MapChunkHeader>(mcnk_pos + 8)->flags.flags.lq_ocean = 1;
break;
default: // river
adt.GetPointer<MapChunkHeader>(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<mclq, int> const& a, std::pair<mclq, int> 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<char>(current_pos).get(), &mclq_layer.first, sizeof(mclq));
current_pos += sizeof(mclq);
}
}
}
void ChunkWater::autoGen(MapChunk *chunk, float factor) void ChunkWater::autoGen(MapChunk *chunk, float factor)
{ {

View File

@@ -32,6 +32,7 @@ public:
void from_mclq(std::vector<mclq>& layers); void from_mclq(std::vector<mclq>& layers);
void fromFile(BlizzardArchive::ClientFile& f, size_t basePos); void fromFile(BlizzardArchive::ClientFile& f, size_t basePos);
void save(util::sExtendableArray& adt, int base_pos, int& header_pos, int& current_pos); 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 bool is_visible ( const float& cull_distance
, const math::frustum& frustum , const math::frustum& frustum
@@ -76,6 +77,8 @@ public:
float xbase, zbase; float xbase, zbase;
int layer_count() const { return _layers.size(); }
private: private:
MH2O_Attributes attributes; MH2O_Attributes attributes;

View File

@@ -135,7 +135,7 @@ MapChunk::MapChunk(MapTile* maintile, BlizzardArchive::ClientFile* f, bool bigAl
f->read(&tmp_chunk_header, sizeof(MapChunkHeader)); 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; areaID = tmp_chunk_header.areaid;
zbase = tmp_chunk_header.zpos; zbase = tmp_chunk_header.zpos;
@@ -1432,7 +1432,8 @@ void MapChunk::save(util::sExtendableArray& lADTFile
, int& lMCIN_Position , int& lMCIN_Position
, std::map<std::string, int> &lTextures , std::map<std::string, int> &lTextures
, std::vector<WMOInstance*> &lObjectInstances , std::vector<WMOInstance*> &lObjectInstances
, std::vector<ModelInstance*>& lModelInstances) , std::vector<ModelInstance*>& lModelInstances
, bool use_mclq_liquids)
{ {
int lID; int lID;
int lMCNK_Size = 0x80; int lMCNK_Size = 0x80;
@@ -1445,9 +1446,9 @@ void MapChunk::save(util::sExtendableArray& lADTFile
// lADTFile.Insert(lCurrentPosition + 8, 0x80, reinterpret_cast<char*>(&(header))); // lADTFile.Insert(lCurrentPosition + 8, 0x80, reinterpret_cast<char*>(&(header)));
auto const lMCNK_header = lADTFile.GetPointer<MapChunkHeader>(lCurrentPosition + 8); auto const lMCNK_header = lADTFile.GetPointer<MapChunkHeader>(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->ix = px;
lMCNK_header->iy = py; lMCNK_header->iy = py;
lMCNK_header->zpos = zbase * -1.0f + ZEROPOINT; lMCNK_header->zpos = zbase * -1.0f + ZEROPOINT;
@@ -1690,7 +1691,8 @@ void MapChunk::save(util::sExtendableArray& lADTFile
// MCSH // MCSH
if (has_shadows()) 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; int lMCSH_Size = 0x200;
lADTFile.Extend(8 + lMCSH_Size); lADTFile.Extend(8 + lMCSH_Size);
@@ -1709,7 +1711,8 @@ void MapChunk::save(util::sExtendableArray& lADTFile
} }
else 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->ofsShadow = 0;
header_ptr->sizeShadow = 0; header_ptr->sizeShadow = 0;
} }
@@ -1731,10 +1734,42 @@ void MapChunk::save(util::sExtendableArray& lADTFile
lCurrentPosition += 8 + lMCAL_Size; lCurrentPosition += 8 + lMCAL_Size;
lMCNK_Size += 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<6F>t save.
// clear MCLQ liquid flags (0x4, 0x8, 0x10, 0x20)
header_ptr->flags.value &= 0xFFFFFFC3;
lCurrentPosition += 8;
lMCNK_Size += 8;
}
}
// MCSE // MCSE
int lMCSE_Size = sizeof(ENTRY_MCSE) * sound_emitters.size(); int lMCSE_Size = sizeof(ENTRY_MCSE) * sound_emitters.size();

View File

@@ -201,7 +201,9 @@ public:
, int &lMCIN_Position , int &lMCIN_Position
, std::map<std::string, int> &lTextures , std::map<std::string, int> &lTextures
, std::vector<WMOInstance*> &lObjectInstances , std::vector<WMOInstance*> &lObjectInstances
, std::vector<ModelInstance*>& lModelInstances); , std::vector<ModelInstance*>& lModelInstances
, bool use_mclq_liquids
);
// fix the gaps with the chunk to the left // fix the gaps with the chunk to the left
bool fixGapLeft(const MapChunk* chunk); bool fixGapLeft(const MapChunk* chunk);

View File

@@ -121,7 +121,7 @@ struct ENTRY_MODF
}; };
struct MapChunkHeader { struct MapChunkHeader {
uint32_t flags = 0; mcnk_flags flags;
uint32_t ix = 0; uint32_t ix = 0;
uint32_t iy = 0; uint32_t iy = 0;
uint32_t nLayers = 0; uint32_t nLayers = 0;

View File

@@ -529,6 +529,24 @@ void MapTile::getVertexInternal(float x, float z, glm::vec3* v)
/// --- Only saving related below this line. -------------------------- /// --- Only saving related below this line. --------------------------
void MapTile::saveTile(World* world) 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; Log << "Saving ADT \"" << _file_key.stringRepr() << "\"." << std::endl;
@@ -848,14 +866,17 @@ void MapTile::saveTile(World* world)
lCurrentPosition += 8 + lMODF_Size; lCurrentPosition += 8 + lMODF_Size;
//MH2O //MH2O
Water.saveToFile(lADTFile, lMHDR_Position, lCurrentPosition); if (!save_using_mclq_liquids)
{
Water.saveToFile(lADTFile, lMHDR_Position, lCurrentPosition);
}
// MCNK // MCNK
for (int y = 0; y < 16; ++y) for (int y = 0; y < 16; ++y)
{ {
for (int x = 0; x < 16; ++x) 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. // begin with.
f.setBuffer(lADTFile.data_up_to(lCurrentPosition)); // cleaning unused nulls at the end of file f.setBuffer(lADTFile.data_up_to(lCurrentPosition)); // cleaning unused nulls at the end of file
f.save(); 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(); lObjectInstances.clear();

View File

@@ -107,8 +107,13 @@ public:
bool GetVertex(float x, float z, glm::vec3 *V); bool GetVertex(float x, float z, glm::vec3 *V);
void getVertexInternal(float x, float z, glm::vec3* v); void getVertexInternal(float x, float z, glm::vec3* v);
void saveTile(World*);
void CropWater(); void CropWater();
void saveTile(World* world);
private:
void save(World* world, bool save_using_mclq_liquids);
public:
bool isTile(int pX, int pZ); bool isTile(int pX, int pZ);

View File

@@ -174,7 +174,7 @@ liquid_layer::liquid_layer(ChunkWater* chunk
update_min_max(); update_min_max();
} }
liquid_layer::liquid_layer(liquid_layer&& other) liquid_layer::liquid_layer(liquid_layer&& other) noexcept
: _liquid_id(other._liquid_id) : _liquid_id(other._liquid_id)
, _liquid_vertex_format(other._liquid_vertex_format) , _liquid_vertex_format(other._liquid_vertex_format)
, _minimum(other._minimum) , _minimum(other._minimum)
@@ -207,7 +207,7 @@ liquid_layer::liquid_layer(liquid_layer const& other)
changeLiquidID(_liquid_id); 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_id, other._liquid_id);
std::swap(_liquid_vertex_format, other._liquid_vertex_format); std::swap(_liquid_vertex_format, other._liquid_vertex_format);
@@ -395,14 +395,20 @@ void liquid_layer::changeLiquidID(int id)
switch (_liquid_type) switch (_liquid_type)
{ {
case liquid_basic_types_magma: case liquid_basic_types_magma:
_mclq_liquid_type = 6;
_liquid_vertex_format = 1;
break;
case liquid_basic_types_slime: case liquid_basic_types_slime:
_mclq_liquid_type = 3;
_liquid_vertex_format = HEIGHT_UV; _liquid_vertex_format = HEIGHT_UV;
break; break;
case liquid_basic_types_ocean: // ocean case liquid_basic_types_ocean: // ocean
_liquid_vertex_format = DEPTH; _liquid_vertex_format = DEPTH;
_mclq_liquid_type = 1;
break; break;
default: default: // river
_liquid_vertex_format = HEIGHT_DEPTH; _liquid_vertex_format = HEIGHT_DEPTH;
_mclq_liquid_type = 4;
break; break;
} }
} }
@@ -650,6 +656,62 @@ bool liquid_layer::check_fatigue() const
return true; 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::uint16_t>(std::min(_vertices[i].uv.x * 255.f, 65535.f));
mclq_data.vertices[i].magma.y = static_cast<std::uint16_t>(std::min(_vertices[i].uv.y * 255.f, 65535.f));
}
else
{
mclq_data.vertices[i].water.depth = static_cast<std::uint8_t>(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) void liquid_layer::update_attributes(MH2O_Attributes& attributes)
{ {
if (check_fatigue()) if (check_fatigue())

View File

@@ -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(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 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); liquid_layer& operator=(liquid_layer const& other);
void save(util::sExtendableArray& adt, int base_pos, int& info_pos, int& current_pos) const; 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 update_attributes(MH2O_Attributes& attributes);
void changeLiquidID(int id); void changeLiquidID(int id);
@@ -75,6 +76,9 @@ public:
float min() const { return _minimum; } float min() const { return _minimum; }
float max() const { return _maximum; } float max() const { return _maximum; }
int liquidID() const { return _liquid_id; } 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 // used for fatigue calculation
bool subchunk_at_max_depth(int x, int z) const; bool subchunk_at_max_depth(int x, int z) const;
@@ -105,6 +109,7 @@ public:
ChunkWater* getChunk() { return _chunk; }; ChunkWater* getChunk() { return _chunk; };
bool has_fatigue() const { return _fatigue_enabled; } bool has_fatigue() const { return _fatigue_enabled; }
private: private:
void create_vertices(float height); void create_vertices(float height);
@@ -120,6 +125,7 @@ private:
int _liquid_id; int _liquid_id;
int _liquid_type; int _liquid_type;
int _liquid_vertex_format; int _liquid_vertex_format;
int _mclq_liquid_type;
float _minimum; float _minimum;
float _maximum; float _maximum;

View File

@@ -219,6 +219,7 @@ namespace Noggit
ui->_additional_file_loading_log->setChecked( ui->_additional_file_loading_log->setChecked(
_settings->value("additional_file_loading_log", false).toBool()); _settings->value("additional_file_loading_log", false).toBool());
ui->_keyboard_locale->setCurrentText(_settings->value("keyboard_locale", "QWERTY").toString()); 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->_theme->setCurrentText(_settings->value("theme", "Dark").toString());
ui->assetBrowserBgCol->setColor(_settings->value("assetBrowser/background_color", ui->assetBrowserBgCol->setColor(_settings->value("assetBrowser/background_color",
@@ -305,6 +306,7 @@ namespace Noggit
_settings->setValue("systemWindowFrame", ui->_systemWindowFrame->isChecked()); _settings->setValue("systemWindowFrame", ui->_systemWindowFrame->isChecked());
_settings->setValue("nativeMenubar", ui->_nativeMenubar->isChecked()); _settings->setValue("nativeMenubar", ui->_nativeMenubar->isChecked());
_settings->setValue("classicUI", ui->_classic_ui->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 #ifdef USE_MYSQL_UID_STORAGE
_settings->setValue ("project/mysql/enabled", ui->MySQL_box->isChecked()); _settings->setValue ("project/mysql/enabled", ui->MySQL_box->isChecked());

View File

@@ -478,7 +478,7 @@
<number>64</number> <number>64</number>
</property> </property>
<property name="value"> <property name="value">
<number>1</number> <number>2</number>
</property> </property>
</widget> </widget>
</item> </item>
@@ -585,6 +585,15 @@
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_32"> <layout class="QVBoxLayout" name="verticalLayout_32">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_9"> <layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="leftMargin"> <property name="leftMargin">
@@ -768,6 +777,23 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1">
<widget class="QCheckBox" name="_use_mclq_liquids_export">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Use MCLQ Liquids (vanilla/BC) export</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>