Merge branch 'wdl' into 'noggit-shadowlands'

wdl generator

See merge request prophecy-rp/noggit-red!20
This commit is contained in:
Intemporel
2022-12-02 18:29:16 +00:00
6 changed files with 446 additions and 34 deletions

View File

@@ -1102,6 +1102,21 @@ void MapView::setupFileMenu()
ADD_ACTION (file_menu, "Save current tile", "Ctrl+Shift+S", [this] { save(save_mode::current); emit saved();});
ADD_ACTION (file_menu, "Save changed tiles", QKeySequence::Save, [this] { save(save_mode::changed); emit saved(); });
ADD_ACTION (file_menu, "Save all tiles", "Ctrl+Shift+A", [this] { save(save_mode::all); emit saved(); });
ADD_ACTION(file_menu, "Generate new WDL", "", [this]
{
QMessageBox prompt;
prompt.setIcon(QMessageBox::Warning);
prompt.setWindowFlags(Qt::WindowStaysOnTopHint);
prompt.setText(std::string("Warning!\nThis will attempt to load all tiles in the map to generate a new WDL."
"\nThis is likely to crash if there is any issue with any tile, it is recommended that you save your work first. Only use this if you really need a fresh WDL.").c_str());
prompt.setInformativeText(std::string("Are you sure ?").c_str());
prompt.setStandardButtons(QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No);
prompt.setDefaultButton(QMessageBox::No);
bool answer = prompt.exec() == QMessageBox::StandardButton::Yes;
if (answer)
_world->horizon.save_wdl(_world.get(), true);
}
);
ADD_ACTION ( file_menu
, "Reload tile"
@@ -5202,7 +5217,10 @@ void MapView::save(save_mode mode)
case save_mode::current: _world->mapIndex.saveTile(TileIndex(_camera.position), _world.get()); break;
case save_mode::changed: _world->mapIndex.saveChanged(_world.get()); break;
case save_mode::all: _world->mapIndex.saveall(_world.get()); break;
}
}
// write wdl, we update wdl data prior in the mapIndex saving fucntions above
_world->horizon.save_wdl(_world.get());
NOGGIT_ACTION_MGR->purge();
AsyncLoader::instance().reset_object_fail();

View File

@@ -5,11 +5,13 @@
#include <noggit/Log.h>
#include <noggit/application/NoggitApplication.hpp>
#include <noggit/map_index.hpp>
#include <noggit/MapTile.h>
#include <noggit/World.h>
#include <opengl/context.hpp>
#include <opengl/context.inl>
#include <sstream>
#include <bitset>
struct color
{
@@ -126,10 +128,37 @@ map_horizon::map_horizon(const std::string& basename, const MapIndex * const ind
}
// todo: handle those too ?
case 'MWMO':
case 'MWID':
case 'MODF':
{
{
char const* lCurPos = reinterpret_cast<char const*>(wdl_file.getPointer());
char const* lEnd = lCurPos + size;
while (lCurPos < lEnd)
{
mWMOFilenames.push_back(BlizzardArchive::ClientData::normalizeFilenameInternal(std::string(lCurPos)));
lCurPos += strlen(lCurPos) + 1;
}
}
wdl_file.seekRelative(size);
break;
}
case 'MWID':
wdl_file.seekRelative(size);
break;
// TODO
case 'MODF':
{
wdl_file.seekRelative(size);
break;
// {
// ENTRY_MODF const* modf_ptr = reinterpret_cast<ENTRY_MODF const*>(wdl_file.getPointer());
// for (unsigned int i = 0; i < size / sizeof(ENTRY_MODF); ++i)
// {
// lWMOInstances.push_back(modf_ptr[i]);
// }
// }
// break;
}
case 'MAOF':
{
assert(size == 64 * 64 * sizeof(uint32_t));
@@ -159,9 +188,18 @@ map_horizon::map_horizon(const std::string& basename, const MapIndex * const ind
//! \todo There also is MAHO giving holes into this heightmap.
wdl_file.read(_tiles[y][x]->height_17, 17 * 17 * sizeof(int16_t));
wdl_file.read(_tiles[y][x]->height_16, 16 * 16 * sizeof(int16_t));
wdl_file.read(&fourcc, 4);
if (fourcc == 'MAHO')
{
wdl_file.read(&size, 4);
assert(size == 0x20);
wdl_file.read(_tiles[y][x]->holes, 16 * sizeof(int16_t));
}
}
}
done = true;
break;
}
@@ -174,40 +212,242 @@ map_horizon::map_horizon(const std::string& basename, const MapIndex * const ind
wdl_file.close();
_qt_minimap = QImage (16 * 64, 16 * 64, QImage::Format_ARGB32);
_qt_minimap.fill (Qt::transparent);
set_minimap(index);
}
for (size_t y (0); y < 64; ++y)
{
for (size_t x (0); x < 64; ++x)
void map_horizon::set_minimap(const MapIndex* const index)
{
_qt_minimap = QImage(16 * 64, 16 * 64, QImage::Format_ARGB32);
_qt_minimap.fill(Qt::transparent);
for (size_t y(0); y < 64; ++y)
{
if (_tiles[y][x])
{
//! \todo There also is a second heightmap appended which has additional 16*16 pixels.
//! \todo There also is MAHO giving holes into this heightmap.
for (size_t x(0); x < 64; ++x)
{
if (_tiles[y][x])
{
//! \todo There also is a second heightmap appended which has additional 16*16 pixels.
//! \todo There also is MAHO giving holes into this heightmap.
for (size_t j (0); j < 16; ++j)
{
for (size_t i (0); i < 16; ++i)
{
//! \todo R and B are inverted here
_qt_minimap.setPixel(x * 16 + i, y * 16 + j, color_for_height(_tiles[y][x]->height_17[j][i]));
}
for (size_t j(0); j < 16; ++j)
{
for (size_t i(0); i < 16; ++i)
{
//! \todo R and B are inverted here
_qt_minimap.setPixel(x * 16 + i, y * 16 + j, color_for_height(_tiles[y][x]->height_17[j][i]));
}
}
}
// the adt exist but there's no data in the wdl
else if (index->hasTile(TileIndex(x, y)))
{
for (size_t j(0); j < 16; ++j)
{
for (size_t i(0); i < 16; ++i)
{
_qt_minimap.setPixel(x * 16 + i, y * 16 + j, color(200, 100, 25));
}
}
}
}
}
// the adt exist but there's no data in the wdl
else if (index->hasTile(TileIndex(x, y)))
{
for (size_t j(0); j < 16; ++j)
{
for (size_t i(0); i < 16; ++i)
{
_qt_minimap.setPixel(x * 16 + i, y * 16 + j, color(200, 100, 25));
}
}
}
}
}
}
Noggit::map_horizon_tile* map_horizon::get_horizon_tile(int y, int x)
{
return _tiles[y][x].get();
}
int16_t map_horizon::getWdlheight(MapTile* tile, float x, float y)
{
int cx = std::min(std::max(static_cast<int>(x / CHUNKSIZE), 0), 15);
int cy = std::min(std::max(static_cast<int>(y / CHUNKSIZE), 0), 15);
x -= cx * CHUNKSIZE;
y -= cy * CHUNKSIZE;
int row = static_cast<int>(y / (UNITSIZE * 0.5f) + 0.5f);
int col = static_cast<int>((x - UNITSIZE * 0.5f * (row % 2)) / UNITSIZE + 0.5f);
bool inner = (row % 2) == 1;
if (row < 0 || col < 0 || row > 16 || col >(inner ? 8 : 9))
return 0;
// truncate and clamp the float value
auto chunk = tile->getChunk(cx, cy);
// float height = heights[cy * 16 + cx][17 * (row / 2) + (inner ? 9 : 0) + col];
float height = chunk->getHeightmap()[17 * (row / 2) + (inner ? 9 : 0) + col].y;
return std::min(std::max(static_cast<int16_t>(height), static_cast<int16_t>(SHRT_MIN)), static_cast<int16_t>(SHRT_MAX));
}
void map_horizon::update_horizon_tile(MapTile* mTile)
{
auto tile_index = mTile->index;
// calculate the heightmap as a short array
float x, y;
for (int i = 0; i < 17; i++)
{
for (int j = 0; j < 17; j++)
{
// outer - correct
x = j * CHUNKSIZE;
y = i * CHUNKSIZE;
if (!_tiles[tile_index.z][tile_index.x].get()) // tile has not been initialised
// continue;
{
_tiles[tile_index.z][tile_index.x] = std::make_unique<map_horizon_tile>();
// do we need to use memcpy as well ?
}
// only works for initialised
_tiles[tile_index.z][tile_index.x].get()->height_17[i][j] = getWdlheight(mTile, x, y);
// inner - close enough; correct values appear to use some form of averaging
if (i < 16 && j < 16)
_tiles[tile_index.z][tile_index.x].get()->height_16[i][j] = getWdlheight(mTile, x + CHUNKSIZE / 2.0f, y + CHUNKSIZE / 2.0f);
}
}
// Holes
for (int i = 0; i < 16; ++i)
{
std::bitset<16>wdlHoleMask(0);
for (int j = 0; j < 16; ++j)
{
auto chunk = mTile->getChunk(j, i);
// the ordering seems to be : short array = Y axis, flags values = X axis and the values are for a whole chunk.
std::bitset<16> holeBits(chunk->getHoleMask());
if (holeBits.count() == 16) // if all holes are set in a chunk
wdlHoleMask.set(j, true);
}
_tiles[tile_index.z][tile_index.x].get()->holes[i] = static_cast<int16_t>(wdlHoleMask.to_ulong());
}
}
void map_horizon::save_wdl(World* world, bool regenerate)
{
world->wait_for_all_tile_updates();
std::stringstream filename;
filename << "World\\Maps\\" << world->basename << "\\" << world->basename << ".wdl";
//Log << "Saving WDL \"" << filename << "\"." << std::endl;
sExtendableArray wdlFile = sExtendableArray();
int curPos = 0;
// MVER
// {
wdlFile.Extend(8 + 0x4);
SetChunkHeader(wdlFile, curPos, 'MVER', 4);
// MVER data
*(wdlFile.GetPointer<int>(8)) = 18; // write version 18
curPos += 8 + 0x4;
// }
// MWMO
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MWMO', 0);
curPos += 8;
// }
// MWID
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MWID', 0);
curPos += 8;
// }
// TODO : MODF
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MODF', 0);
curPos += 8;
// }
//uint32_t mare_offsets[64][64] = { 0 };
// MAOF
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MAOF', 64 * 64 * 4);
curPos += 8;
wdlFile.Extend(64 * 64 * 4);
uint mareoffset = curPos + 64 * 64 * 4;
for (int y = 0; y < 64; ++y)
{
for (int x = 0; x < 64; ++x)
{
TileIndex index(x, y);
bool has_tile = world->mapIndex.hasTile(index);
// write offset in MAOF entry
*(wdlFile.GetPointer<uint>(curPos)) = has_tile ? mareoffset : 0;
if (has_tile)
{
// MARE Header
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, mareoffset, 'MARE', (2 * (17 * 17)) + (2 * (16 * 16))); // outer heights+inner heights
mareoffset += 8;
// this might be invalid if map had no WDL
Noggit::map_horizon_tile* horizon_tile = get_horizon_tile(y, x);
// laod tile and extract WDL data
if (!horizon_tile || regenerate)
{
bool unload = !world->mapIndex.tileLoaded(index) && !world->mapIndex.tileAwaitingLoading(index);
MapTile* mTile = world->mapIndex.loadTile(index);
if (mTile)
mTile->wait_until_loaded();
update_horizon_tile(mTile);
if (unload)
world->mapIndex.unloadTile(index);
auto test = get_horizon_tile(y, x);
horizon_tile = get_horizon_tile(y, x);
}
if (!horizon_tile)
{
return; // failed to generate data somehow
LogError << "Failed to generate the WDL file." << std::endl;
}
wdlFile.Insert(mareoffset, sizeof(Noggit::map_horizon_tile::height_17), reinterpret_cast<char*>(&horizon_tile->height_17));
mareoffset += sizeof(Noggit::map_horizon_tile::height_17);
wdlFile.Insert(mareoffset, sizeof(Noggit::map_horizon_tile::height_16), reinterpret_cast<char*>(&horizon_tile->height_16));
mareoffset += sizeof(Noggit::map_horizon_tile::height_16);
// MAHO (maparea holes) MAHO was added in WOTLK ?
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, mareoffset, 'MAHO', (2 * 16)); // 1 hole mask for each chunk
mareoffset += 8;
wdlFile.Extend(32);
for (int i = 0; i < 16; ++i)
{
wdlFile.Insert(mareoffset, 2, (char*)&horizon_tile->holes[i]);
mareoffset += 2;
}
}
curPos += 4;
}
}
BlizzardArchive::ClientFile f(filename.str(), Noggit::Application::NoggitApplication::instance()->clientData(),
BlizzardArchive::ClientFile::NEW_FILE);
f.setBuffer(wdlFile.data);
f.save();
f.close();
set_minimap(&world->mapIndex);
}
map_horizon::minimap::minimap(const map_horizon& horizon)

View File

@@ -15,6 +15,9 @@
#include <memory>
class MapIndex;
class MapTile;
class MapView;
class World;
namespace Noggit
{
@@ -23,6 +26,7 @@ struct map_horizon_tile
{
int16_t height_17[17][17];
int16_t height_16[16][16];
int16_t holes[16];
};
struct map_horizon_batch
@@ -76,11 +80,24 @@ public:
map_horizon(const std::string& basename, const MapIndex * const index);
void set_minimap(const MapIndex* const index);
Noggit::map_horizon_tile* get_horizon_tile(int y, int x);
QImage _qt_minimap;
void update_horizon_tile(MapTile* mTile);
void save_wdl(World* world, bool regenerate = false);
private:
int16_t getWdlheight(MapTile* tile, float x, float y);
std::string _filename;
std::vector<std::string> mWMOFilenames;
// std::vector<ENTRY_MODF> lWMOInstances;
std::unique_ptr<map_horizon_tile> _tiles[64][64];
};

View File

@@ -177,6 +177,7 @@ void MapIndex::saveall (World* world)
for (MapTile* tile : loaded_tiles())
{
world->horizon.update_horizon_tile(tile);
tile->saveTile(world);
tile->changed = false;
}
@@ -462,6 +463,7 @@ void MapIndex::saveTile(const TileIndex& tile, World* world, bool save_unloaded)
if (tileLoaded(tile))
{
saveMaxUID();
world->horizon.update_horizon_tile(mTiles[tile.z][tile.x].tile.get());
mTiles[tile.z][tile.x].tile->saveTile(world);
}
}
@@ -516,6 +518,7 @@ void MapIndex::saveChanged (World* world, bool save_unloaded)
{
if (tile->changed.load())
{
world->horizon.update_horizon_tile(tile);
tile->saveTile(world);
tile->changed = false;
}
@@ -1205,3 +1208,111 @@ void MapIndex::set_basename(const std::string &pBasename)
}
}
}
void MapIndex::create_empty_wdl()
{
// for new map creation, creates a new WDL with all heights as 0
std::stringstream filename;
filename << "World\\Maps\\" << basename << "\\" << basename << ".wdl"; // mapIndex.basename ?
//Log << "Saving WDL \"" << filename << "\"." << std::endl;
sExtendableArray wdlFile = sExtendableArray();
int curPos = 0;
// MVER
// {
wdlFile.Extend(8 + 0x4);
SetChunkHeader(wdlFile, curPos, 'MVER', 4);
// MVER data
*(wdlFile.GetPointer<int>(8)) = 18; // write version 18
curPos += 8 + 0x4;
// }
// MWMO
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MWMO', 0);
curPos += 8;
// }
// MWID
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MWID', 0);
curPos += 8;
// }
// MODF
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MODF', 0);
curPos += 8;
// }
uint32_t mare_offsets[4096] = { 0 }; // [64][64];
// MAOF
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MAOF', 64 * 64 * 4);
curPos += 8;
uint32_t mareoffset = curPos + 64 * 64 * 4;
for (int y = 0; y < 64; ++y)
{
for (int x = 0; x < 64; ++x)
{
TileIndex index(x, y);
bool has_tile = hasTile(index);
// if (tile_exists)
if (has_tile) // TODO check if tile exists
{
// write offset in MAOF entry
wdlFile.Insert(curPos, 4, (char*)&mareoffset);
mare_offsets[y * 64 + x] = mareoffset;
mareoffset += 1138; // mare + maho
}
else
wdlFile.Extend(4);
curPos += 4;
}
}
for (auto offset : mare_offsets)
{
if (!offset)
continue;
// MARE
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MARE', (2 * (17 * 17)) + (2 * (16 * 16))); // outer heights+inner heights
curPos += 8;
// write inner and outer heights
wdlFile.Extend((2 * (17 * 17)) + (2 * (16 * 16)));
curPos += (2 * (17 * 17)) + (2 * (16 * 16));
// }
// MAHO (maparea holes)
// {
wdlFile.Extend(8);
SetChunkHeader(wdlFile, curPos, 'MAHO', 2 * 16); // 1 hole mask for each chunk
curPos += 8;
wdlFile.Extend(32);
curPos += 32;
}
BlizzardArchive::ClientFile f(filename.str(), Noggit::Application::NoggitApplication::instance()->clientData(),
BlizzardArchive::ClientFile::NEW_FILE);
f.setBuffer(wdlFile.data);
f.save();
f.close();
}

View File

@@ -186,6 +186,8 @@ public:
void set_basename(const std::string& pBasename);
void create_empty_wdl();
void enterTile(const TileIndex& tile);
MapTile *loadTile(const TileIndex& tile, bool reloading = false);

View File

@@ -344,6 +344,26 @@ void MapCreationWizard::selectMap(int map_id)
auto timeOffset = record.Columns["TimeOffset"].Value;
_world = new World(directoryName, map_id, Noggit::NoggitRenderContext::MAP_VIEW);
// check if map has a wdl and prompt to create a new one
std::stringstream filename;
filename << "World\\Maps\\" << _world->basename << "\\" << _world->basename << ".wdl";
if (!Application::NoggitApplication::instance()->clientData()->exists(filename.str()))
{
QMessageBox prompt;
prompt.setText(std::string("This map has no existing horizon data (.wdl file).").c_str());
prompt.setInformativeText(std::string("Do you want to generate a new .wdl file ?").c_str());
prompt.setStandardButtons(QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No);
prompt.setDefaultButton(QMessageBox::Yes);
bool answer = prompt.exec() == QMessageBox::StandardButton::Yes;
if (answer)
{
_world->horizon.save_wdl(_world, true);
_world->horizon.set_minimap(&_world->mapIndex);
// _world = new World(directoryName, map_id, Noggit::NoggitRenderContext::MAP_VIEW); // refresh minimap
}
}
_minimap_widget->world(_world);
_directory->setText(QString::fromStdString(directoryName));
@@ -458,11 +478,15 @@ void MapCreationWizard::saveCurrentEntry()
}
// Save ADTs and WDT to disk
// _world->mapIndex.create_empty_wdl();
_world->mapIndex.setBigAlpha(_is_big_alpha->isChecked());
_world->setBasename(_directory->text().toStdString());
_world->mapIndex.set_sort_models_by_size_class(_sort_by_size_cat->isChecked());
_world->mapIndex.saveChanged(_world, true);
_world->mapIndex.save();
_world->mapIndex.save(); // save wdt file
// create default wdl
if (_is_new_record)
_world->mapIndex.create_empty_wdl();
// Save Map.dbc record
DBCFile::Record record = _is_new_record ? gMapDB.addRecord(_cur_map_id) : gMapDB.getByID(_cur_map_id);