diff --git a/source/common_windows.cpp b/source/common_windows.cpp index 395ec8d2..2e876cf1 100644 --- a/source/common_windows.cpp +++ b/source/common_windows.cpp @@ -33,6 +33,8 @@ #include "common_windows.h" #include "positionctrl.h" +#include "iominimap.h" + #ifdef _MSC_VER #pragma warning(disable:4018) // signed/unsigned mismatch #endif @@ -491,6 +493,15 @@ ExportMiniMapWindow::ExportMiniMapWindow(wxWindow* parent, Editor& editor) : tmpsizer->Add(file_name_text_field, 1, wxALL, 5); sizer->Add(tmpsizer, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5); + // Format options + wxArrayString format_choices; + format_choices.Add(".otmm (Client Minimap)"); + format_choices.Add(".png (PNG Image)"); + format_choices.Add(".bmp (Bitmap Image)"); + format_options = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, format_choices); + format_options->SetSelection(0); + tmpsizer->Add(format_options, 1, wxALL, 5); + // Export options wxArrayString choices; choices.Add("All Floors"); @@ -553,59 +564,29 @@ void ExportMiniMapWindow::OnFileNameChanged(wxKeyEvent& event) void ExportMiniMapWindow::OnClickOK(wxCommandEvent& WXUNUSED(event)) { - g_gui.CreateLoadBar("Exporting minimap"); + g_gui.CreateLoadBar("Exporting minimap..."); - try - { - FileName directory(directory_text_field->GetValue()); - g_settings.setString(Config::MINIMAP_EXPORT_DIR, directory_text_field->GetValue().ToStdString()); - - switch(floor_options->GetSelection()) - { - case 0: { // All floors - for(int floor = 0; floor < rme::MapLayers; ++floor) { - g_gui.SetLoadScale(int(floor*(100.f/16.f)), int((floor+1)*(100.f/16.f))); - FileName file(file_name_text_field->GetValue() + "_" + i2ws(floor) + ".bmp"); - file.Normalize(wxPATH_NORM_ALL, directory.GetFullPath()); - editor.exportMiniMap(file, floor, true); - } - break; - } - - case 1: { // Ground floor - FileName file(file_name_text_field->GetValue() + "_" + i2ws(rme::MapGroundLayer) + ".bmp"); - file.Normalize(wxPATH_NORM_ALL, directory.GetFullPath()); - editor.exportMiniMap(file, rme::MapGroundLayer, true); - break; - } + auto format = static_cast(format_options->GetSelection()); + auto mode = static_cast(floor_options->GetSelection()); + std::string directory = directory_text_field->GetValue().ToStdString(); + std::string file_name = file_name_text_field->GetValue().ToStdString(); + int floor = floor_number->GetValue(); - case 2: { // Specific floors - int floor = floor_number->GetValue(); - FileName file(file_name_text_field->GetValue() + "_" + i2ws(floor) + ".bmp"); - file.Normalize(wxPATH_NORM_ALL, directory.GetFullPath()); - editor.exportMiniMap(file, floor, true); - break; - } + g_settings.setString(Config::MINIMAP_EXPORT_DIR, directory); - case 3: { // Selected area - editor.exportSelectionAsMiniMap(directory, file_name_text_field->GetValue()); - break; - } - } - } - catch(std::bad_alloc&) - { - g_gui.PopupDialog("Error", "There is not enough memory available to complete the operation.", wxOK); + IOMinimap io(&editor, format, mode, true); + if (!io.saveMinimap(directory, file_name, floor)) { + g_gui.PopupDialog("Error", io.getError(), wxOK); } g_gui.DestroyLoadBar(); - EndModal(1); + EndModal(wxID_OK); } void ExportMiniMapWindow::OnClickCancel(wxCommandEvent& WXUNUSED(event)) { // Just close this window - EndModal(0); + EndModal(wxID_CANCEL); } void ExportMiniMapWindow::CheckValues() diff --git a/source/common_windows.h b/source/common_windows.h index d31efca0..e1757735 100644 --- a/source/common_windows.h +++ b/source/common_windows.h @@ -127,6 +127,7 @@ class ExportMiniMapWindow : public wxDialog Editor& editor; + wxChoice* format_options; wxStaticText* error_field; wxTextCtrl* directory_text_field; wxTextCtrl* file_name_text_field; diff --git a/source/editor.cpp b/source/editor.cpp index 180293b1..7f701dfd 100644 --- a/source/editor.cpp +++ b/source/editor.cpp @@ -440,101 +440,6 @@ bool Editor::importMiniMap(FileName filename, int import, int import_x_offset, i return false; } -bool Editor::exportMiniMap(FileName filename, int floor /*= rme::MapGroundLayer*/, bool displaydialog) -{ - return map.exportMinimap(filename, floor, displaydialog); -} - -bool Editor::exportSelectionAsMiniMap(FileName directory, wxString fileName) -{ - if(!directory.Exists() || !directory.IsDirWritable()) - return false; - - int min_x = rme::MapMaxWidth + 1, min_y = rme::MapMaxHeight + 1, min_z = rme::MapMaxLayer + 1; - int max_x = 0, max_y = 0, max_z = 0; - - const TileSet& tiles = selection.getTiles(); - for(Tile* tile : tiles) { - if(tile->empty()) - continue; - - const Position& pos = tile->getPosition(); - - if(pos.x < min_x) - min_x = pos.x; - if(pos.x > max_x) - max_x = pos.x; - - if(pos.y < min_y) - min_y = pos.y; - if(pos.y > max_y) - max_y = pos.y; - - if(pos.z < min_z) - min_z = pos.z; - if(pos.z > max_z) - max_z = pos.z; - } - - int numtiles = (max_x - min_x) * (max_y - min_y); - int minimap_width = max_x - min_x + 1; - int minimap_height = max_y - min_y + 1; - - if(numtiles == 0) - return false; - - if(minimap_width > 2048 || minimap_height > 2048) { - g_gui.PopupDialog("Error", "Minimap size greater than 2048px.", wxOK); - return false; - } - - int tiles_iterated = 0; - - for(int z = min_z; z <= max_z; z++) { - uint8_t* pixels = newd uint8_t[minimap_width * minimap_height * 3]; // 3 bytes per pixel - memset(pixels, 0, minimap_width * minimap_height * 3); - - for(Tile* tile : tiles) { - if(tile->getZ() != z) - continue; - - ++tiles_iterated; - if(tiles_iterated % 8192 == 0) - g_gui.SetLoadDone(int(tiles_iterated / double(tiles.size()) * 90.0)); - - if(tile->empty()) - continue; - - uint8_t color = 0; - - for(Item* item : tile->items) { - if(item->getMiniMapColor() != 0) { - color = item->getMiniMapColor(); - break; - } - } - - if(color == 0 && tile->hasGround()) - color = tile->ground->getMiniMapColor(); - - uint32_t index = ((tile->getY() - min_y) * minimap_width + (tile->getX() - min_x)) * 3; - - pixels[index] = (uint8_t)(int(color / 36) % 6 * 51); // red - pixels[index + 1] = (uint8_t)(int(color / 6) % 6 * 51); // green - pixels[index + 2] = (uint8_t)(color % 6 * 51); // blue - } - - FileName file(fileName + "_" + i2ws(z) + ".png"); - file.Normalize(wxPATH_NORM_ALL, directory.GetFullPath()); - wxImage* image = newd wxImage(minimap_width, minimap_height, pixels, true); - image->SaveFile(file.GetFullPath(), wxBITMAP_TYPE_PNG); - image->Destroy(); - delete[] pixels; - } - - return true; -} - bool Editor::importMap(FileName filename, int import_x_offset, int import_y_offset, int import_z_offset, ImportType house_import_type, ImportType spawn_import_type) { selection.clear(); diff --git a/source/editor.h b/source/editor.h index 2de871ac..97911c8a 100644 --- a/source/editor.h +++ b/source/editor.h @@ -84,8 +84,6 @@ class Editor wxString getLoaderError() const { return map.getError(); } bool importMap(FileName filename, int import_x_offset, int import_y_offset, int import_z_offset, ImportType house_import_type, ImportType spawn_import_type); bool importMiniMap(FileName filename, int import, int import_x_offset, int import_y_offset, int import_z_offset); - bool exportMiniMap(FileName filename, int floor /*= rme::MapGroundLayer*/, bool displaydialog); - bool exportSelectionAsMiniMap(FileName directory, wxString fileName); ActionQueue* getHistoryActions() const noexcept { return actionQueue; } Action* createAction(ActionIdentifier type); diff --git a/source/filehandle.cpp b/source/filehandle.cpp index a0abb1a7..eb9c1ef5 100644 --- a/source/filehandle.cpp +++ b/source/filehandle.cpp @@ -26,6 +26,22 @@ uint8_t NodeFileWriteHandle::NODE_START = ::NODE_START; uint8_t NodeFileWriteHandle::NODE_END = ::NODE_END; uint8_t NodeFileWriteHandle::ESCAPE_CHAR = ::ESCAPE_CHAR; +bool FileHandle::seek(size_t offset, int origin) +{ + if(file) { + return fseek(file, static_cast(offset), origin) == 0; + } + return false; +} + +size_t FileHandle::tell() +{ + if(file) { + return ftell(file); + } + return 0; +} + void FileHandle::close() { if(file) { @@ -126,16 +142,6 @@ bool FileReadHandle::getLongString(std::string& str) return getRAW(str, sz); } -bool FileReadHandle::seek(size_t offset) -{ - return fseek(file, long(offset), SEEK_SET) == 0; -} - -bool FileReadHandle::seekRelative(size_t offset) -{ - return fseek(file, long(offset), SEEK_CUR) == 0; -} - //============================================================================= // Node file read handle @@ -553,6 +559,13 @@ bool FileWriteHandle::addRAW(const uint8_t* ptr, size_t sz) return ferror(file) == 0; } +void FileWriteHandle::flush() +{ + if(file) { + fflush(file); + } +} + //============================================================================= // Disk based node file write handle diff --git a/source/filehandle.h b/source/filehandle.h index bd9d856f..45f6fda4 100644 --- a/source/filehandle.h +++ b/source/filehandle.h @@ -56,6 +56,10 @@ class FileHandle FileHandle() : error_code(FILE_NO_ERROR), file(nullptr) {} virtual ~FileHandle() { close(); } + bool seek(size_t offset, int origin = SEEK_SET); + size_t tell(); + FORCEINLINE void skip(size_t offset) { seek(offset, SEEK_CUR); } + // Ensures we don't accidentally copy it. FileHandle(const FileHandle &) = delete; FileHandle &operator=(const FileHandle &) = delete; @@ -64,6 +68,7 @@ class FileHandle virtual bool isOpen() { return file != nullptr; } virtual bool isOk() { return isOpen() && error_code == FILE_NO_ERROR && ferror(file) == 0; } std::string getErrorMessage(); + public: FileHandleError error_code; FILE* file; @@ -87,11 +92,8 @@ class FileReadHandle : public FileHandle bool getLongString(std::string& str); virtual void close(); - bool seek(size_t offset); - bool seekRelative(size_t offset); - FORCEINLINE void skip(size_t offset) { seekRelative(offset); } size_t size() { return file_size; } - size_t tell() { if(file) return ftell(file); return 0; } + protected: size_t file_size; @@ -241,6 +243,7 @@ class FileWriteHandle : public FileHandle bool addRAW(const std::string& str); bool addRAW(const uint8_t* ptr, size_t sz); bool addRAW(const char* c) { return addRAW(reinterpret_cast(c), strlen(c)); } + void flush(); protected: template @@ -301,17 +304,20 @@ class NodeFileWriteHandle : public FileHandle } }; -class DiskNodeFileWriteHandle : public NodeFileWriteHandle { +class DiskNodeFileWriteHandle : public NodeFileWriteHandle +{ public: DiskNodeFileWriteHandle(const std::string& name, const std::string& identifier); virtual ~DiskNodeFileWriteHandle(); virtual void close(); + protected: virtual void renewCache(); }; -class MemoryNodeFileWriteHandle : public NodeFileWriteHandle { +class MemoryNodeFileWriteHandle : public NodeFileWriteHandle +{ public: MemoryNodeFileWriteHandle(); virtual ~MemoryNodeFileWriteHandle(); @@ -321,6 +327,7 @@ class MemoryNodeFileWriteHandle : public NodeFileWriteHandle { uint8_t* getMemory(); size_t getSize(); + protected: virtual void renewCache(); }; diff --git a/source/graphics.cpp b/source/graphics.cpp index 8d94a63d..e075c222 100644 --- a/source/graphics.cpp +++ b/source/graphics.cpp @@ -634,7 +634,6 @@ bool GraphicManager::loadSpriteMetadataFlags(FileReadHandle& file, GameSprite* s case DatFlagChargeable: break; - case DatFlagGround: case DatFlagWritable: case DatFlagWritableOnce: case DatFlagCloth: @@ -643,6 +642,12 @@ bool GraphicManager::loadSpriteMetadataFlags(FileReadHandle& file, GameSprite* s file.skip(2); break; + case DatFlagGround: + uint16_t speed; + file.getU16(speed); + sType->ground_speed = speed; + break; + case DatFlagLight: { SpriteLight light; uint16_t intensity; @@ -759,7 +764,7 @@ bool GraphicManager::loadSpriteData(const FileName& datafile, wxString& error, w wxString ss; ss << "items.spr: Duplicate GameSprite id " << id; warnings.push_back(ss); - fh.seekRelative(size); + fh.skip(size); } else { spr->id = id; spr->size = size; @@ -771,7 +776,7 @@ bool GraphicManager::loadSpriteData(const FileName& datafile, wxString& error, w } } } else { - fh.seekRelative(size); + fh.skip(size); } } #undef safe_get @@ -887,6 +892,7 @@ GameSprite::GameSprite() : frames(0), numsprites(0), animator(nullptr), + ground_speed(0), draw_height(0), minimap_color(0) { diff --git a/source/graphics.h b/source/graphics.h index 375bb0cc..6ce6f8db 100644 --- a/source/graphics.h +++ b/source/graphics.h @@ -201,6 +201,7 @@ class GameSprite : public Sprite Animator* animator; + uint16_t ground_speed; uint16_t draw_height; wxPoint draw_offset; uint16_t minimap_color; diff --git a/source/iominimap.cpp b/source/iominimap.cpp new file mode 100644 index 00000000..12c1d014 --- /dev/null +++ b/source/iominimap.cpp @@ -0,0 +1,406 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#include "main.h" + +#include "iominimap.h" + +#include "tile.h" +#include "filehandle.h" +#include "editor.h" +#include "gui.h" + +#include +#include + +void MinimapBlock::updateTile(int x, int y, const MinimapTile& tile) +{ + m_tiles[getTileIndex(x, y)] = tile; +} + +IOMinimap::IOMinimap(Editor* editor, MinimapExportFormat format, MinimapExportMode mode, bool updateLoadbar) : + m_editor(editor), + m_format(format), + m_mode(mode), + m_updateLoadbar(updateLoadbar) +{ +} + +bool IOMinimap::saveMinimap(const std::string& directory, const std::string& name, int floor) +{ + if(m_mode == MinimapExportMode::AllFloors || m_mode == MinimapExportMode::SelectedArea) { + floor = -1; + } else if(m_mode == MinimapExportMode::GroundFloor) { + floor = rme::MapGroundLayer; + } else if(m_mode == MinimapExportMode::SpecificFloor) { + if(floor < rme::MapMinLayer || floor > rme::MapMaxLayer) { + floor = rme::MapGroundLayer; + } + } + + m_floor = floor; + + if (m_format == MinimapExportFormat::Otmm) { + return saveOtmm(wxFileName(directory, name + ".otmm")); + } + return saveImage(directory, name); +} + +bool IOMinimap::saveOtmm(const wxFileName& file) +{ + try + { + FileWriteHandle writer(file.GetFullPath().ToStdString()); + if(!writer.isOk()) { + //error("Unable to open file %s for save minimap", file); + return false; + } + + //TODO: compression flag with zlib + uint32_t flags = 0; + + // header + writer.addU32(OTMM_SIGNATURE); + writer.addU16(0); // data start, will be overwritten later + writer.addU16(OTMM_VERSION); + writer.addU32(flags); + + // version 1 header + writer.addString("OTMM 1.0"); // description + + // go back and rewrite where the map data starts + uint32_t start = writer.tell(); + writer.seek(4); + writer.addU16(start); + writer.seek(start); + + unsigned long blockSize = MMBLOCK_SIZE * MMBLOCK_SIZE * sizeof(MinimapTile); + std::vector buffer(compressBound(blockSize)); + constexpr int COMPRESS_LEVEL = 3; + + readBlocks(); + + for(uint8_t z = 0; z <= rme::MapMaxLayer; ++z) { + for(auto& it : m_blocks[z]) { + int index = it.first; + auto& block = it.second; + + // write index pos + uint16_t x = static_cast((index % (65536 / MMBLOCK_SIZE)) * MMBLOCK_SIZE); + uint16_t y = static_cast((index / (65536 / MMBLOCK_SIZE)) * MMBLOCK_SIZE); + writer.addU16(x); + writer.addU16(y); + writer.addU8(z); + + unsigned long len = blockSize; + int ret = compress2(buffer.data(), &len, (uint8_t*)&block.getTiles(), blockSize, COMPRESS_LEVEL); + assert(ret == Z_OK); + writer.addU16(len); + writer.addRAW(buffer.data(), len); + } + m_blocks[z].clear(); + } + + // end of file is an invalid pos + writer.addU16(65535); + writer.addU16(65535); + writer.addU8(255); + + writer.flush(); + writer.close(); + } catch(std::exception& e) { + m_error = wxString::Format("failed to save OTMM minimap: %s", e.what()); + return false; + } + + return true; +} + +bool IOMinimap::saveImage(const std::string& directory, const std::string& name) +{ + try + { + switch(m_mode) + { + case MinimapExportMode::AllFloors: + case MinimapExportMode::GroundFloor: + case MinimapExportMode::SpecificFloor: { + exportMinimap(directory); + break; + } + case MinimapExportMode::SelectedArea: { + exportSelection(directory, name); + break; + } + } + } + catch(std::bad_alloc&) + { + m_error = "There is not enough memory available to complete the operation."; + } + + return true; +} + +bool IOMinimap::exportMinimap(const std::string& directory) +{ + auto& map = m_editor->getMap(); + if(map.size() == 0) { + return true; + } + + wxRect bounds[rme::MapLayers]; + int min_z = m_floor == -1 ? 0 : m_floor; + int max_z = m_floor == -1 ? rme::MapMaxLayer : m_floor; + + for (size_t z = min_z; z <= max_z; z++) { + auto& rect = bounds[z]; + rect.x = rme::MapMaxWidth + 1; + rect.y = rme::MapMaxHeight + 1; + rect.width = 0; + rect.height = 0; + } + + for(auto it = map.begin(); it != map.end(); ++it) { + auto tile = (*it)->get(); + if(!tile || (!tile->ground && tile->items.empty())) { + continue; + } + + const auto& position = tile->getPosition(); + auto& rect = bounds[position.z]; + if(position.x < rect.x) { + rect.x = position.x; + } + if(position.y < rect.y) { + rect.y = position.y; + } + if (position.x > rect.width) { + rect.width = position.x; + } + if (position.y > rect.height) { + rect.height = position.y; + } + } + + constexpr int image_size = 1024; + constexpr int pixels_size = image_size * image_size * rme::PixelFormatRGB; + uint8_t* pixels = new uint8_t[pixels_size]; + auto image = new wxImage(image_size, image_size, pixels, true); + + for(size_t z = min_z; z <= max_z; z++) { + auto& rect = bounds[z]; + if(rect.IsEmpty()) { + continue; + } + + for (int h = 0; h < rme::MapMaxHeight; h += image_size) { + for (int w = 0; w < rme::MapMaxWidth; w += image_size) { + if (w < rect.x || w > rect.width || h < rect.y || h > rect.height) { + continue; + } + + bool empty = true; + memset(pixels, 0, pixels_size); + + int index = 0; + for (int y = 0; y < image_size; y++) { + for (int x = 0; x < image_size; x++) { + auto tile = map.getTile(w + x, h + y, z); + if(!tile || (!tile->ground && tile->items.empty())) { + index += rme::PixelFormatRGB; + continue; + } + uint8_t color = tile->getMiniMapColor(); + pixels[index ] = (uint8_t)(static_cast(color / 36) % 6 * 51); // red + pixels[index+1] = (uint8_t)(static_cast(color / 6) % 6 * 51); // green + pixels[index+2] = (uint8_t)(color % 6 * 51); // blue + index += rme::PixelFormatRGB; + empty = false; + } + } + + if (!empty) { + image->SetData(pixels, true); + wxString extension = m_format == MinimapExportFormat::Png ? "png" : "bmp"; + wxBitmapType type = m_format == MinimapExportFormat::Png ? wxBITMAP_TYPE_PNG : wxBITMAP_TYPE_BMP; + wxFileName file = wxString::Format("%d-%d-%d.%s", h, w, z, extension); + file.Normalize(wxPATH_NORM_ALL, directory); + image->SaveFile(file.GetFullPath(), type); + } + } + } + } + + image->Destroy(); + delete[] pixels; + return true; +} + +bool IOMinimap::exportSelection(const std::string& directory, const std::string& name) +{ + int min_x = rme::MapMaxWidth + 1; + int min_y = rme::MapMaxHeight + 1; + int min_z = rme::MapMaxLayer + 1; + int max_x = 0, max_y = 0, max_z = 0; + + const auto& selection = m_editor->getSelection(); + const auto& tiles = selection.getTiles(); + + for(auto tile : tiles) { + if(!tile || (!tile->ground && tile->items.empty())) { + continue; + } + + const auto& position = tile->getPosition(); + if(position.x < min_x) { + min_x = position.x; + } + if(position.x > max_x) { + max_x = position.x; + } + + if(position.y < min_y) { + min_y = position.y; + } + if(position.y > max_y) { + max_y = position.y; + } + + if(position.z < min_z) { + min_z = position.z; + } + if(position.z > max_z) { + max_z = position.z; + } + } + + int numtiles = (max_x - min_x) * (max_y - min_y); + if(numtiles == 0) { + return false; + } + + int image_width = max_x - min_x + 1; + int image_height = max_y - min_y + 1; + if(image_width > 2048 || image_height > 2048) { + g_gui.PopupDialog("Error", "Minimap size greater than 2048px.", wxOK); + return false; + } + + int pixels_size = image_width * image_height * rme::PixelFormatRGB; + uint8_t* pixels = new uint8_t[pixels_size]; + auto image = new wxImage(image_width, image_height, pixels, true); + + int tiles_iterated = 0; + for(int z = min_z; z <= max_z; z++) { + bool empty = true; + memset(pixels, 0, pixels_size); + for(auto tile : tiles) { + if(tile->getZ() != z) { + continue; + } + + if (m_updateLoadbar) { + tiles_iterated++; + if(tiles_iterated % 8192 == 0) { + g_gui.SetLoadDone(int(tiles_iterated / double(tiles.size()) * 90.0)); + } + } + + if(!tile->ground && tile->items.empty()) { + continue; + } + + uint8_t color = tile->getMiniMapColor(); + uint32_t index = ((tile->getY() - min_y) * image_width + (tile->getX() - min_x)) * 3; + pixels[index ] = (uint8_t)(static_cast(color / 36) % 6 * 51); // red + pixels[index+1] = (uint8_t)(static_cast(color / 6) % 6 * 51); // green + pixels[index+2] = (uint8_t)(color % 6 * 51); // blue + empty = false; + } + + if (!empty) { + image->SetData(pixels, true); + wxString extension = m_format == MinimapExportFormat::Png ? "png" : "bmp"; + wxBitmapType type = m_format == MinimapExportFormat::Png ? wxBITMAP_TYPE_PNG : wxBITMAP_TYPE_BMP; + wxFileName file = wxString::Format("%s-%d.%s", name, z, extension); + file.Normalize(wxPATH_NORM_ALL, directory); + image->SaveFile(file.GetFullPath(), type); + } + } + + image->Destroy(); + delete[] pixels; + return true; +} + +void IOMinimap::readBlocks() +{ + if (m_mode == MinimapExportMode::SelectedArea && !m_editor->hasSelection()) { + return; + } + + auto& map = m_editor->getMap(); + + int tiles_iterated = 0; + for(auto it = map.begin(); it != map.end(); ++it) { + auto tile = (*it)->get(); + + if (m_updateLoadbar) { + ++tiles_iterated; + if (tiles_iterated % 8192 == 0) { + g_gui.SetLoadDone(int(tiles_iterated / double(map.size()) * 90.0)); + } + } + + if(!tile || (!tile->ground && tile->items.empty())) { + continue; + } + + const auto& position = tile->getPosition(); + + if (m_mode == MinimapExportMode::SelectedArea) { + if (!tile->isSelected()) { + continue; + } + } else if (m_floor != -1 && position.z != m_floor) { + continue; + } + + MinimapTile minimapTile; + minimapTile.color = tile->getMiniMapColor(); + minimapTile.flags |= MinimapTileWasSeen; + if (tile->isBlocking()) { + minimapTile.flags |= MinimapTileNotWalkable; + } + //if (!tile->isPathable()) { + //minimapTile.flags |= MinimapTileNotPathable; + //} + minimapTile.speed = std::min((int)std::ceil(tile->getGroundSpeed() / 10.f), 0xFF); + + auto& blocks = m_blocks[position.z]; + uint32_t index = getBlockIndex(position); + if (blocks.find(index) == blocks.end()) { + blocks.insert({ index, MinimapBlock() }); + } + + auto& block = blocks.at(index); + int offset_x = position.x - (position.x % MMBLOCK_SIZE); + int offset_y = position.y - (position.y % MMBLOCK_SIZE); + block.updateTile(position.x - offset_x, position.y - offset_y, minimapTile); + } +} diff --git a/source/iominimap.h b/source/iominimap.h new file mode 100644 index 00000000..7dfca939 --- /dev/null +++ b/source/iominimap.h @@ -0,0 +1,97 @@ +////////////////////////////////////////////////////////////////////// +// This file is part of Remere's Map Editor +////////////////////////////////////////////////////////////////////// +// Remere's Map Editor is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Remere's Map Editor is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +////////////////////////////////////////////////////////////////////// + +#ifndef RME_OTMM_H_ +#define RME_OTMM_H_ + +#include "map.h" + +enum class MinimapExportFormat { + Otmm, + Png, + Bmp +}; + +enum class MinimapExportMode { + AllFloors, + GroundFloor, + SpecificFloor, + SelectedArea +}; + +enum { + MMBLOCK_SIZE = 64, + OTMM_SIGNATURE = 0x4D4d544F, + OTMM_VERSION = 1 +}; + +enum MinimapTileFlags { + MinimapTileWasSeen = 1, + MinimapTileNotPathable = 2, + MinimapTileNotWalkable = 4 +}; + +#pragma pack(push,1) // disable memory alignment +struct MinimapTile +{ + uint8_t flags = 0; + uint8_t color = INVALID_MINIMAP_COLOR; + uint8_t speed = 10; +}; + +class MinimapBlock +{ +public: + void updateTile(int x, int y, const MinimapTile& tile); + MinimapTile& getTile(int x, int y) { return m_tiles[getTileIndex(x,y)]; } + inline uint32_t getTileIndex(int x, int y) const noexcept { return ((y % MMBLOCK_SIZE) * MMBLOCK_SIZE) + (x % MMBLOCK_SIZE); } + const std::array& getTiles() const noexcept { return m_tiles; } + +private: + std::array m_tiles; +}; +#pragma pack(pop) + +class IOMinimap +{ +public: + IOMinimap(Editor* editor, MinimapExportFormat format, MinimapExportMode mode, bool updateLoadbar); + + bool saveMinimap(const std::string& directory, const std::string& name, int floor = -1); + + const std::string& getError() const noexcept { return m_error; } + +private: + bool saveOtmm(const wxFileName& file); + bool saveImage(const std::string& directory, const std::string& name); + bool exportMinimap(const std::string& directory); + bool exportSelection(const std::string& directory, const std::string& name); + void readBlocks(); + inline uint32_t getBlockIndex(const Position& pos) { + return ((pos.y / MMBLOCK_SIZE) * (65536 / MMBLOCK_SIZE)) + (pos.x / MMBLOCK_SIZE); + } + + Editor* m_editor; + MinimapExportFormat m_format; + MinimapExportMode m_mode; + bool m_updateLoadbar = false; + int m_floor = -1; + std::unordered_map m_blocks[rme::MapLayers]; + std::string m_error; +}; + +#endif diff --git a/source/item.cpp b/source/item.cpp index 7d89ee14..2f7416e5 100644 --- a/source/item.cpp +++ b/source/item.cpp @@ -232,6 +232,15 @@ wxPoint Item::getDrawOffset() const return wxPoint(0, 0); } +uint16_t Item::getGroundSpeed() const +{ + const auto& type = g_items.getItemType(id); + if(type.sprite) { + return type.sprite->ground_speed; + } + return 0; +} + bool Item::hasLight() const { const ItemType& type = g_items.getItemType(id); diff --git a/source/item.h b/source/item.h index fbf4e780..8cdcdec5 100644 --- a/source/item.h +++ b/source/item.h @@ -177,6 +177,8 @@ class Item : public ItemAttributes uint8_t getMiniMapColor() const; wxPoint getDrawOffset() const; + uint16_t getGroundSpeed() const; + bool hasLight() const; SpriteLight getLight() const; diff --git a/source/main_menubar.cpp b/source/main_menubar.cpp index ffe4d98f..699ab920 100644 --- a/source/main_menubar.cpp +++ b/source/main_menubar.cpp @@ -806,11 +806,12 @@ void MainMenuBar::OnImportMinimap(wxCommandEvent& WXUNUSED(event)) void MainMenuBar::OnExportMinimap(wxCommandEvent& WXUNUSED(event)) { - if(g_gui.GetCurrentEditor()) { - ExportMiniMapWindow dlg(frame, *g_gui.GetCurrentEditor()); - dlg.ShowModal(); - dlg.Destroy(); + if(!g_gui.IsEditorOpen()) { + return; } + + ExportMiniMapWindow dialog(frame, *g_gui.GetCurrentEditor()); + dialog.ShowModal(); } void MainMenuBar::OnDebugViewDat(wxCommandEvent& WXUNUSED(event)) diff --git a/source/tile.cpp b/source/tile.cpp index d7dad5c4..44605782 100644 --- a/source/tile.cpp +++ b/source/tile.cpp @@ -167,6 +167,14 @@ bool Tile::hasProperty(enum ITEMPROPERTY prop) const return false; } +uint16_t Tile::getGroundSpeed() const noexcept +{ + if(ground && !ground->isMetaItem()) { + return ground->getGroundSpeed(); + } + return 0; +} + int Tile::getIndexOf(Item* item) const { if(!item) diff --git a/source/tile.h b/source/tile.h index 4230f7f0..cbfd33c3 100644 --- a/source/tile.h +++ b/source/tile.h @@ -78,6 +78,8 @@ class Tile int getY() const noexcept { return location->getY(); } int getZ() const noexcept { return location->getZ(); } + uint16_t getGroundSpeed() const noexcept; + public: //Functions // Absorb the other tile into this tile void merge(Tile* other); diff --git a/vcproj/Project/RME.vcxproj b/vcproj/Project/RME.vcxproj index 9b4c7f5b..adf8dc05 100644 --- a/vcproj/Project/RME.vcxproj +++ b/vcproj/Project/RME.vcxproj @@ -217,6 +217,7 @@ + @@ -227,6 +228,7 @@ +