multiple changes related to minimap generator
This commit is contained in:
@@ -137,6 +137,11 @@ void MapView::setToolPropertyWidgetVisibility(editing_mode mode)
|
|||||||
|
|
||||||
void MapView::ResetSelectedObjectRotation()
|
void MapView::ResetSelectedObjectRotation()
|
||||||
{
|
{
|
||||||
|
if (terrainMode != editing_mode::object)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& selection : _world->current_selection())
|
for (auto& selection : _world->current_selection())
|
||||||
{
|
{
|
||||||
if (selection.which() == eEntry_WMO)
|
if (selection.which() == eEntry_WMO)
|
||||||
@@ -161,6 +166,11 @@ void MapView::ResetSelectedObjectRotation()
|
|||||||
|
|
||||||
void MapView::snap_selected_models_to_the_ground()
|
void MapView::snap_selected_models_to_the_ground()
|
||||||
{
|
{
|
||||||
|
if (terrainMode != editing_mode::object)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_world->snap_selected_models_to_the_ground();
|
_world->snap_selected_models_to_the_ground();
|
||||||
_rotation_editor_need_update = true;
|
_rotation_editor_need_update = true;
|
||||||
}
|
}
|
||||||
@@ -168,6 +178,11 @@ void MapView::snap_selected_models_to_the_ground()
|
|||||||
|
|
||||||
void MapView::DeleteSelectedObject()
|
void MapView::DeleteSelectedObject()
|
||||||
{
|
{
|
||||||
|
if (terrainMode != editing_mode::object)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
opengl::context::scoped_setter const _ (::gl, context());
|
opengl::context::scoped_setter const _ (::gl, context());
|
||||||
|
|
||||||
@@ -269,7 +284,8 @@ void MapView::createGUI()
|
|||||||
_tool_properties_docks.insert(_vertex_shading_dock);
|
_tool_properties_docks.insert(_vertex_shading_dock);
|
||||||
|
|
||||||
_minimap_tool_dock = new QDockWidget("Minimap Creator", this);
|
_minimap_tool_dock = new QDockWidget("Minimap Creator", this);
|
||||||
minimapTool = new noggit::ui::MinimapCreator(this);
|
_minimap_tool_dock->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||||
|
minimapTool = new noggit::ui::MinimapCreator(this, _world.get(), _minimap_tool_dock);
|
||||||
_minimap_tool_dock->setWidget(minimapTool);
|
_minimap_tool_dock->setWidget(minimapTool);
|
||||||
_tool_properties_docks.insert(_minimap_tool_dock);
|
_tool_properties_docks.insert(_minimap_tool_dock);
|
||||||
|
|
||||||
@@ -1537,18 +1553,21 @@ void MapView::initializeGL()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MapView::saveMinimap(noggit::MinimapRenderSettings* settings)
|
void MapView::saveMinimap(MinimapRenderSettings* settings)
|
||||||
{
|
{
|
||||||
|
// This convoluted logic is necessary here if we want to draw minimaps in the same OpenGL context.
|
||||||
|
// And we do, to avoid loading geometry twice. Even though, offscreen one in the background would be nice.
|
||||||
|
// The idea is, if rendering fails due to unfinished loading, we skip to the next frame until we are able to render.
|
||||||
|
|
||||||
switch (settings->export_mode)
|
switch (settings->export_mode)
|
||||||
{
|
{
|
||||||
case noggit::MinimapGenMode::CURRENT_ADT:
|
case MinimapGenMode::CURRENT_ADT:
|
||||||
{
|
{
|
||||||
tile_index tile = tile_index(_camera.position);
|
tile_index tile = tile_index(_camera.position);
|
||||||
|
|
||||||
if (_world->mapIndex.hasTile(tile))
|
if (_world->mapIndex.hasTile(tile))
|
||||||
{
|
{
|
||||||
mmap_render_success = _world->saveMinimap(512, 512, tile);
|
mmap_render_success = _world->saveMinimap(tile, settings);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1562,14 +1581,13 @@ void MapView::saveMinimap(noggit::MinimapRenderSettings* settings)
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case noggit::MinimapGenMode::MAP:
|
case MinimapGenMode::MAP:
|
||||||
{
|
{
|
||||||
// increment tile indices here
|
|
||||||
tile_index tile = tile_index(mmap_render_index / 64, mmap_render_index % 64);
|
tile_index tile = tile_index(mmap_render_index / 64, mmap_render_index % 64);
|
||||||
|
|
||||||
if (_world->mapIndex.hasTile(tile))
|
if (_world->mapIndex.hasTile(tile))
|
||||||
{
|
{
|
||||||
mmap_render_success = _world->saveMinimap(512, 512, tile);
|
mmap_render_success = _world->saveMinimap(tile, settings);
|
||||||
|
|
||||||
if (mmap_render_success)
|
if (mmap_render_success)
|
||||||
{
|
{
|
||||||
@@ -1577,7 +1595,8 @@ void MapView::saveMinimap(noggit::MinimapRenderSettings* settings)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{do
|
{
|
||||||
|
do
|
||||||
{
|
{
|
||||||
mmap_render_index++;
|
mmap_render_index++;
|
||||||
tile.x = mmap_render_index / 64;
|
tile.x = mmap_render_index / 64;
|
||||||
@@ -1596,8 +1615,52 @@ void MapView::saveMinimap(noggit::MinimapRenderSettings* settings)
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case noggit::MinimapGenMode::SELECTED_ADTS:
|
case MinimapGenMode::SELECTED_ADTS:
|
||||||
{
|
{
|
||||||
|
auto selected_tiles = minimapTool->getSelectedTiles();
|
||||||
|
|
||||||
|
while (mmap_render_index < 4096)
|
||||||
|
{
|
||||||
|
|
||||||
|
bool is_selected = selected_tiles->at(mmap_render_index);
|
||||||
|
|
||||||
|
if (is_selected)
|
||||||
|
{
|
||||||
|
tile_index tile = tile_index(mmap_render_index / 64, mmap_render_index % 64);
|
||||||
|
|
||||||
|
if (_world->mapIndex.hasTile(tile))
|
||||||
|
{
|
||||||
|
mmap_render_success = _world->saveMinimap(tile, settings);
|
||||||
|
|
||||||
|
if (mmap_render_success)
|
||||||
|
{
|
||||||
|
mmap_render_index++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mmap_render_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mmap_render_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mmap_render_success && mmap_render_index >= 4095)
|
||||||
|
{
|
||||||
|
saving_minimap = false;
|
||||||
|
mmap_render_index = 0;
|
||||||
|
mmap_render_success = false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2339,8 +2402,20 @@ void MapView::doSelection (bool selectTerrainOnly, bool mouseMove)
|
|||||||
{
|
{
|
||||||
auto const& hit (results.front().second);
|
auto const& hit (results.front().second);
|
||||||
|
|
||||||
if (terrainMode == editing_mode::object)
|
if (terrainMode == editing_mode::object || terrainMode == editing_mode::minimap)
|
||||||
{
|
{
|
||||||
|
float radius = 0.0f;
|
||||||
|
switch (terrainMode)
|
||||||
|
{
|
||||||
|
case editing_mode::object:
|
||||||
|
radius = objectEditor->brushRadius();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case editing_mode::minimap:
|
||||||
|
radius = minimapTool->brushRadius();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (_mod_shift_down)
|
if (_mod_shift_down)
|
||||||
{
|
{
|
||||||
if (hit.which() == eEntry_Model || hit.which() == eEntry_WMO)
|
if (hit.which() == eEntry_Model || hit.which() == eEntry_WMO)
|
||||||
@@ -2356,14 +2431,14 @@ void MapView::doSelection (bool selectTerrainOnly, bool mouseMove)
|
|||||||
}
|
}
|
||||||
else if (hit.which() == eEntry_MapChunk)
|
else if (hit.which() == eEntry_MapChunk)
|
||||||
{
|
{
|
||||||
_world->range_add_to_selection(_cursor_pos, objectEditor->brushRadius(), false);
|
_world->range_add_to_selection(_cursor_pos, radius, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_mod_ctrl_down)
|
else if (_mod_ctrl_down)
|
||||||
{
|
{
|
||||||
if (hit.which() == eEntry_MapChunk)
|
if (hit.which() == eEntry_MapChunk)
|
||||||
{
|
{
|
||||||
_world->range_add_to_selection(_cursor_pos, objectEditor->brushRadius(), true);
|
_world->range_add_to_selection(_cursor_pos, radius, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!_mod_space_down && !_mod_alt_down && !_mod_ctrl_down)
|
else if (!_mod_space_down && !_mod_alt_down && !_mod_ctrl_down)
|
||||||
@@ -2477,6 +2552,9 @@ void MapView::draw_map()
|
|||||||
case editing_mode::object:
|
case editing_mode::object:
|
||||||
radius = objectEditor->brushRadius();
|
radius = objectEditor->brushRadius();
|
||||||
break;
|
break;
|
||||||
|
case editing_mode::minimap:
|
||||||
|
radius = minimapTool->brushRadius();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \note Select terrain below mouse, if no item selected or the item is map.
|
//! \note Select terrain below mouse, if no item selected or the item is map.
|
||||||
@@ -2881,6 +2959,9 @@ void MapView::mouseMoveEvent (QMouseEvent* event)
|
|||||||
case editing_mode::object:
|
case editing_mode::object:
|
||||||
objectEditor->changeRadius(relative_movement.dx() / XSENS);
|
objectEditor->changeRadius(relative_movement.dx() / XSENS);
|
||||||
break;
|
break;
|
||||||
|
case editing_mode::minimap:
|
||||||
|
minimapTool->changeRadius(relative_movement.dx() / XSENS);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2905,7 +2986,7 @@ void MapView::mouseMoveEvent (QMouseEvent* event)
|
|||||||
|
|
||||||
if (leftMouse && (_mod_shift_down || _mod_ctrl_down))
|
if (leftMouse && (_mod_shift_down || _mod_ctrl_down))
|
||||||
{
|
{
|
||||||
if (terrainMode == editing_mode::object)
|
if (terrainMode == editing_mode::object || terrainMode == editing_mode::minimap)
|
||||||
{
|
{
|
||||||
doSelection(false, true); // Required for radius selection in Object mode
|
doSelection(false, true); // Required for radius selection in Object mode
|
||||||
}
|
}
|
||||||
@@ -2987,7 +3068,7 @@ void MapView::mousePressEvent(QMouseEvent* event)
|
|||||||
|
|
||||||
if (leftMouse)
|
if (leftMouse)
|
||||||
{
|
{
|
||||||
if (terrainMode == editing_mode::object && !_mod_ctrl_down)
|
if ((terrainMode == editing_mode::object || terrainMode == editing_mode::minimap) && !_mod_ctrl_down)
|
||||||
{
|
{
|
||||||
doSelection(false);
|
doSelection(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ public:
|
|||||||
void tick (float dt);
|
void tick (float dt);
|
||||||
void selectModel(std::string const& model);
|
void selectModel(std::string const& model);
|
||||||
void change_selected_wmo_doodadset(int set);
|
void change_selected_wmo_doodadset(int set);
|
||||||
void saveMinimap(noggit::MinimapRenderSettings* settings);
|
void saveMinimap(MinimapRenderSettings* settings);
|
||||||
void initMinimapSave() { saving_minimap = true; };
|
void initMinimapSave() { saving_minimap = true; };
|
||||||
|
|
||||||
void set_editing_mode (editing_mode);
|
void set_editing_mode (editing_mode);
|
||||||
|
|||||||
@@ -1762,6 +1762,7 @@ void World::drawMinimap ( MapTile *tile
|
|||||||
, math::matrix_4x4 const& model_view
|
, math::matrix_4x4 const& model_view
|
||||||
, math::matrix_4x4 const& projection
|
, math::matrix_4x4 const& projection
|
||||||
, math::vector_3d const& camera_pos
|
, math::vector_3d const& camera_pos
|
||||||
|
, MinimapRenderSettings* settings
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (!_display_initialized)
|
if (!_display_initialized)
|
||||||
@@ -1829,7 +1830,7 @@ void World::drawMinimap ( MapTile *tile
|
|||||||
math::vector_3d diffuse_color(skies->color_set[LIGHT_GLOBAL_DIFFUSE] * outdoorLightStats.dayIntensity);
|
math::vector_3d diffuse_color(skies->color_set[LIGHT_GLOBAL_DIFFUSE] * outdoorLightStats.dayIntensity);
|
||||||
math::vector_3d ambient_color(skies->color_set[LIGHT_GLOBAL_AMBIENT] * outdoorLightStats.ambientIntensity);
|
math::vector_3d ambient_color(skies->color_set[LIGHT_GLOBAL_AMBIENT] * outdoorLightStats.ambientIntensity);
|
||||||
|
|
||||||
culldistance = _view_distance;
|
culldistance = 100000.0f;
|
||||||
|
|
||||||
gl.enable(GL_DEPTH_TEST);
|
gl.enable(GL_DEPTH_TEST);
|
||||||
gl.depthFunc(GL_LEQUAL); // less z-fighting artifacts this way, I think
|
gl.depthFunc(GL_LEQUAL); // less z-fighting artifacts this way, I think
|
||||||
@@ -2021,35 +2022,7 @@ void World::drawMinimap ( MapTile *tile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool World::saveMinimap(tile_index const& tile_idx, MinimapRenderSettings* settings)
|
||||||
void World::saveMinimap (int width, int height)
|
|
||||||
{
|
|
||||||
for (size_t z = 0; z < 64; z++)
|
|
||||||
{
|
|
||||||
for (size_t x = 0; x < 64; x++)
|
|
||||||
{
|
|
||||||
tile_index tile(x, z);
|
|
||||||
|
|
||||||
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
||||||
MapTile* mTile = mapIndex.loadTile(tile);
|
|
||||||
|
|
||||||
if (mTile)
|
|
||||||
{
|
|
||||||
mTile->wait_until_loaded();
|
|
||||||
|
|
||||||
//drawMinimap(mTile);
|
|
||||||
|
|
||||||
if (unload)
|
|
||||||
{
|
|
||||||
mapIndex.unloadTile(tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool World::saveMinimap(int width, int height, tile_index const& tile_idx)
|
|
||||||
{
|
{
|
||||||
// Setup framebuffer
|
// Setup framebuffer
|
||||||
QOpenGLFramebufferObjectFormat fmt;
|
QOpenGLFramebufferObjectFormat fmt;
|
||||||
@@ -2057,10 +2030,10 @@ bool World::saveMinimap(int width, int height, tile_index const& tile_idx)
|
|||||||
fmt.setInternalTextureFormat(GL_RGBA8);
|
fmt.setInternalTextureFormat(GL_RGBA8);
|
||||||
fmt.setAttachment(QOpenGLFramebufferObject::Depth);
|
fmt.setAttachment(QOpenGLFramebufferObject::Depth);
|
||||||
|
|
||||||
QOpenGLFramebufferObject pixel_buffer(width, height, fmt);
|
QOpenGLFramebufferObject pixel_buffer(settings->resolution, settings->resolution, fmt);
|
||||||
pixel_buffer.bind();
|
pixel_buffer.bind();
|
||||||
|
|
||||||
gl.viewport(0, 0, width, height);
|
gl.viewport(0, 0, settings->resolution, settings->resolution);
|
||||||
gl.clearColor(.0f, .0f, .0f, 1.f);
|
gl.clearColor(.0f, .0f, .0f, 1.f);
|
||||||
gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
@@ -2087,14 +2060,19 @@ bool World::saveMinimap(int width, int height, tile_index const& tile_idx)
|
|||||||
-TILESIZE / 2.0f,
|
-TILESIZE / 2.0f,
|
||||||
TILESIZE / 2.0f,
|
TILESIZE / 2.0f,
|
||||||
5.f,
|
5.f,
|
||||||
10000.0f
|
100000.0f
|
||||||
);
|
);
|
||||||
|
|
||||||
auto look_at = math::look_at(math::vector_3d(TILESIZE * tile_idx.x + TILESIZE / 2.0f, max_height + 10.0f, TILESIZE * tile_idx.z + TILESIZE / 2.0f),
|
auto look_at = math::look_at(math::vector_3d(TILESIZE * tile_idx.x + TILESIZE / 2.0f, max_height + 10.0f, TILESIZE * tile_idx.z + TILESIZE / 2.0f),
|
||||||
math::vector_3d(TILESIZE * tile_idx.x + TILESIZE / 2.0f, max_height + 9.0f, TILESIZE * tile_idx.z + TILESIZE / 2.0 - 0.005f),
|
math::vector_3d(TILESIZE * tile_idx.x + TILESIZE / 2.0f, max_height + 9.0f, TILESIZE * tile_idx.z + TILESIZE / 2.0 - 0.005f),
|
||||||
math::vector_3d(0.f,1.f, 0.f));
|
math::vector_3d(0.f,1.f, 0.f));
|
||||||
|
|
||||||
drawMinimap(mTile, look_at.transposed(), projection.transposed(), math::vector_3d(TILESIZE * tile_idx.x + TILESIZE / 2.0f, max_height + 15.0f, TILESIZE * tile_idx.z + TILESIZE / 2.0f));
|
drawMinimap(mTile
|
||||||
|
, look_at.transposed()
|
||||||
|
, projection.transposed()
|
||||||
|
, math::vector_3d(TILESIZE * tile_idx.x + TILESIZE / 2.0f
|
||||||
|
, max_height + 15.0f, TILESIZE * tile_idx.z + TILESIZE / 2.0f)
|
||||||
|
, settings);
|
||||||
|
|
||||||
QImage image = pixel_buffer.toImage();
|
QImage image = pixel_buffer.toImage();
|
||||||
image.save(("/Users/sshumakov/Desktop/MinimapGenTest/test_" + std::to_string(tile_idx.x) + "_" + std::to_string(tile_idx.z) + ".png").c_str());
|
image.save(("/Users/sshumakov/Desktop/MinimapGenTest/test_" + std::to_string(tile_idx.x) + "_" + std::to_string(tile_idx.z) + ".png").c_str());
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <noggit/tool_enums.hpp>
|
#include <noggit/tool_enums.hpp>
|
||||||
#include <noggit/world_tile_update_queue.hpp>
|
#include <noggit/world_tile_update_queue.hpp>
|
||||||
#include <noggit/world_model_instances_storage.hpp>
|
#include <noggit/world_model_instances_storage.hpp>
|
||||||
|
#include <noggit/ui/MinimapCreator.hpp>
|
||||||
#include <opengl/primitives.hpp>
|
#include <opengl/primitives.hpp>
|
||||||
#include <opengl/shader.fwd.hpp>
|
#include <opengl/shader.fwd.hpp>
|
||||||
|
|
||||||
@@ -262,12 +263,12 @@ public:
|
|||||||
void updateTilesModel(ModelInstance* m2, model_update type);
|
void updateTilesModel(ModelInstance* m2, model_update type);
|
||||||
void wait_for_all_tile_updates();
|
void wait_for_all_tile_updates();
|
||||||
|
|
||||||
void saveMinimap (int width, int height);
|
bool saveMinimap (tile_index const& tile_idx, MinimapRenderSettings* settings);
|
||||||
bool saveMinimap (int width, int height, tile_index const& tile_idx);
|
|
||||||
void drawMinimap ( MapTile *tile
|
void drawMinimap ( MapTile *tile
|
||||||
, math::matrix_4x4 const& model_view
|
, math::matrix_4x4 const& model_view
|
||||||
, math::matrix_4x4 const& projection
|
, math::matrix_4x4 const& projection
|
||||||
, math::vector_3d const& camera_pos
|
, math::vector_3d const& camera_pos
|
||||||
|
, MinimapRenderSettings* settings
|
||||||
);
|
);
|
||||||
|
|
||||||
void deleteModelInstance(int pUniqueID);
|
void deleteModelInstance(int pUniqueID);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "MinimapCreator.hpp"
|
#include "MinimapCreator.hpp"
|
||||||
|
|
||||||
#include <noggit/MapView.h>
|
#include <noggit/MapView.h>
|
||||||
|
#include <noggit/World.h>
|
||||||
|
|
||||||
#include <util/qt/overload.hpp>
|
#include <util/qt/overload.hpp>
|
||||||
|
|
||||||
@@ -12,6 +13,10 @@
|
|||||||
#include <QButtonGroup>
|
#include <QButtonGroup>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QWheelEvent>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QComboBox>
|
||||||
|
|
||||||
|
|
||||||
namespace noggit
|
namespace noggit
|
||||||
@@ -20,16 +25,40 @@ namespace noggit
|
|||||||
{
|
{
|
||||||
MinimapCreator::MinimapCreator (
|
MinimapCreator::MinimapCreator (
|
||||||
MapView* mapView,
|
MapView* mapView,
|
||||||
|
World* world,
|
||||||
QWidget* parent ) : QWidget(parent)
|
QWidget* parent ) : QWidget(parent)
|
||||||
{
|
{
|
||||||
|
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
auto layout = new QHBoxLayout(this);
|
auto layout = new QHBoxLayout(this);
|
||||||
|
|
||||||
auto layout_left = new QFormLayout (this);
|
// Left side
|
||||||
|
|
||||||
layout->addItem(layout_left);
|
auto layout_left = new QFormLayout (this);
|
||||||
|
layout->addLayout(layout_left);
|
||||||
|
|
||||||
|
auto scroll_minimap = new QScrollArea(this);
|
||||||
|
scroll_minimap->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||||
|
|
||||||
|
_minimap_widget = new minimap_widget(this);
|
||||||
|
layout_left->addWidget(scroll_minimap);
|
||||||
|
|
||||||
|
scroll_minimap->setAlignment(Qt::AlignCenter);
|
||||||
|
scroll_minimap->setWidget(_minimap_widget);
|
||||||
|
scroll_minimap->setWidgetResizable(true);
|
||||||
|
scroll_minimap->setFixedSize(QSize(512, 512));
|
||||||
|
|
||||||
|
|
||||||
|
_minimap_widget->world(world);
|
||||||
|
_minimap_widget->draw_boundaries(true);
|
||||||
|
_minimap_widget->use_selection(&_selected_tiles);
|
||||||
|
|
||||||
|
// Right side
|
||||||
|
|
||||||
|
auto layout_right = new QFormLayout (this);
|
||||||
|
layout->addLayout(layout_right);
|
||||||
|
|
||||||
auto settings_tabs = new QTabWidget (this);
|
auto settings_tabs = new QTabWidget (this);
|
||||||
layout->addWidget(settings_tabs);
|
layout_right->addWidget(settings_tabs);
|
||||||
|
|
||||||
// Generate
|
// Generate
|
||||||
auto generate_widget = new QWidget(this);
|
auto generate_widget = new QWidget(this);
|
||||||
@@ -63,6 +92,17 @@ namespace noggit
|
|||||||
|
|
||||||
auto render_settings_box_layout = new QFormLayout (render_settings_box);
|
auto render_settings_box_layout = new QFormLayout (render_settings_box);
|
||||||
|
|
||||||
|
render_settings_box_layout->addRow(new QLabel("Resolution:"));
|
||||||
|
auto resolution = new QComboBox(this);
|
||||||
|
resolution->addItem("256");
|
||||||
|
resolution->addItem("512");
|
||||||
|
resolution->addItem("1024");
|
||||||
|
resolution->addItem("2048");
|
||||||
|
resolution->addItem("4096");
|
||||||
|
resolution->setCurrentText("512");
|
||||||
|
|
||||||
|
render_settings_box_layout->addRow (resolution);
|
||||||
|
|
||||||
auto draw_models = new QCheckBox("Draw models", render_settings_box);
|
auto draw_models = new QCheckBox("Draw models", render_settings_box);
|
||||||
draw_models->setChecked(_render_settings.draw_m2);
|
draw_models->setChecked(_render_settings.draw_m2);
|
||||||
render_settings_box_layout->addRow (draw_models);
|
render_settings_box_layout->addRow (draw_models);
|
||||||
@@ -105,6 +145,13 @@ namespace noggit
|
|||||||
|
|
||||||
// Render settings
|
// Render settings
|
||||||
|
|
||||||
|
connect ( resolution, &QComboBox::currentTextChanged
|
||||||
|
, [&] (QString s)
|
||||||
|
{
|
||||||
|
_render_settings.resolution = s.toInt();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
connect (draw_models, &QCheckBox::stateChanged, [this] (int s)
|
connect (draw_models, &QCheckBox::stateChanged, [this] (int s)
|
||||||
{
|
{
|
||||||
_render_settings.draw_m2 = s;
|
_render_settings.draw_m2 = s;
|
||||||
@@ -146,7 +193,43 @@ namespace noggit
|
|||||||
mapView->initMinimapSave();
|
mapView->initMinimapSave();
|
||||||
});
|
});
|
||||||
|
|
||||||
setMinimumWidth(sizeHint().width());
|
// Selection
|
||||||
|
|
||||||
|
QObject::connect
|
||||||
|
( _minimap_widget, &minimap_widget::tile_clicked
|
||||||
|
, [this, world] (QPoint tile)
|
||||||
|
{
|
||||||
|
if (QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier))
|
||||||
|
{
|
||||||
|
int x = tile.x() - 1;
|
||||||
|
int y = tile.y() - 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < 3; ++j)
|
||||||
|
{
|
||||||
|
if (world->mapIndex.hasTile(tile_index(x + i, y + j)))
|
||||||
|
{
|
||||||
|
_selected_tiles[64 * (x + i) + (y + j)] = !QApplication::keyboardModifiers().testFlag(
|
||||||
|
Qt::ControlModifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (world->mapIndex.hasTile(tile_index(tile.x(), tile.y())))
|
||||||
|
{
|
||||||
|
_selected_tiles[64 * tile.x() + tile.y()] = !QApplication::keyboardModifiers().testFlag(
|
||||||
|
Qt::ControlModifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinimapCreator::changeRadius(float change)
|
void MinimapCreator::changeRadius(float change)
|
||||||
@@ -156,7 +239,33 @@ namespace noggit
|
|||||||
|
|
||||||
QSize MinimapCreator::sizeHint() const
|
QSize MinimapCreator::sizeHint() const
|
||||||
{
|
{
|
||||||
return QSize(215, height());
|
return QSize(width(), height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimapCreator::wheelEvent(QWheelEvent* event)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
|
||||||
|
{
|
||||||
|
const int degrees = event->angleDelta().y() / 8;
|
||||||
|
int steps = degrees / 15;
|
||||||
|
|
||||||
|
auto base_size = _minimap_widget->width();
|
||||||
|
|
||||||
|
if (steps > 0)
|
||||||
|
{
|
||||||
|
auto new_size = std::max(512, base_size + 64);
|
||||||
|
_minimap_widget->setFixedSize(new_size, new_size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto new_size = std::min(4096, base_size - 64);
|
||||||
|
_minimap_widget->setFixedSize(new_size, new_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
event->ignore();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,57 +12,69 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <noggit/ui/minimap_widget.hpp>
|
||||||
|
|
||||||
class MapView;
|
class MapView;
|
||||||
|
class World;
|
||||||
|
|
||||||
|
enum MinimapGenMode
|
||||||
|
{
|
||||||
|
CURRENT_ADT,
|
||||||
|
SELECTED_ADTS,
|
||||||
|
MAP
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MinimapRenderSettings
|
||||||
|
{
|
||||||
|
MinimapGenMode export_mode; // Export mode
|
||||||
|
|
||||||
|
// Render settings
|
||||||
|
int resolution = 512;
|
||||||
|
bool draw_m2 = false;
|
||||||
|
bool draw_wmo = true;
|
||||||
|
bool draw_water = true;
|
||||||
|
bool draw_adt_grid = false;
|
||||||
|
bool draw_elevation = false;
|
||||||
|
|
||||||
|
// Filtering
|
||||||
|
std::unordered_map<std::string, float> m2_model_filter_include; // filename, size category
|
||||||
|
std::vector<uint32_t> m2_instance_filter_include; // include specific M2 instances
|
||||||
|
std::vector<std::string> wmo_model_filter_exclude; // exclude WMOs by filename
|
||||||
|
std::vector<uint32_t> wmo_instance_filter_exclude; // exclude specific WMO instances
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
namespace noggit
|
namespace noggit
|
||||||
{
|
{
|
||||||
|
|
||||||
enum MinimapGenMode
|
|
||||||
{
|
|
||||||
CURRENT_ADT,
|
|
||||||
SELECTED_ADTS,
|
|
||||||
MAP
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MinimapRenderSettings
|
|
||||||
{
|
|
||||||
MinimapGenMode export_mode; // Export mode
|
|
||||||
|
|
||||||
// Render settings
|
|
||||||
bool draw_m2 = false;
|
|
||||||
bool draw_wmo = true;
|
|
||||||
bool draw_water = true;
|
|
||||||
bool draw_adt_grid = false;
|
|
||||||
bool draw_elevation = false;
|
|
||||||
|
|
||||||
// Filtering
|
|
||||||
std::unordered_map<std::string, float> m2_model_filter_include; // filename, size category
|
|
||||||
std::vector<uint32_t> m2_instance_filter_include; // include specific M2 instances
|
|
||||||
std::vector<std::string> wmo_model_filter_exclude; // exclude WMOs by filename
|
|
||||||
std::vector<uint32_t> wmo_instance_filter_exclude; // exclude specific WMO instances
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace ui
|
namespace ui
|
||||||
{
|
{
|
||||||
class MinimapCreator : public QWidget
|
class MinimapCreator : public QWidget
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MinimapCreator (MapView* mapView, QWidget* parent = nullptr);
|
MinimapCreator (MapView* mapView, World* world, QWidget* parent = nullptr);
|
||||||
|
|
||||||
void changeRadius(float change);
|
void changeRadius(float change);
|
||||||
|
|
||||||
float brushRadius() const { return _radius; }
|
float brushRadius() const { return _radius; }
|
||||||
|
|
||||||
|
std::array<bool, 4096>* getSelectedTiles() { return &_selected_tiles; };
|
||||||
|
|
||||||
MinimapRenderSettings* getMinimapRenderSettings() { return &_render_settings; };
|
MinimapRenderSettings* getMinimapRenderSettings() { return &_render_settings; };
|
||||||
|
|
||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
void wheelEvent(QWheelEvent* event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float _radius = 0.01f;
|
float _radius = 0.01f;
|
||||||
MinimapRenderSettings _render_settings;
|
MinimapRenderSettings _render_settings;
|
||||||
QSlider* _radius_slider;
|
QSlider* _radius_slider;
|
||||||
QDoubleSpinBox* _radius_spin;
|
QDoubleSpinBox* _radius_spin;
|
||||||
|
minimap_widget* _minimap_widget;
|
||||||
|
|
||||||
|
std::array<bool, 4096> _selected_tiles = {false};
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace noggit
|
|||||||
add_tool_icon (editing_mode::water, tr("Water editor"), font_awesome::water);
|
add_tool_icon (editing_mode::water, tr("Water editor"), font_awesome::water);
|
||||||
add_tool_icon (editing_mode::mccv, tr("Shader editor"), font_awesome::eyedropper);
|
add_tool_icon (editing_mode::mccv, tr("Shader editor"), font_awesome::eyedropper);
|
||||||
add_tool_icon (editing_mode::object, tr("Object editor"), font_awesome::cube);
|
add_tool_icon (editing_mode::object, tr("Object editor"), font_awesome::cube);
|
||||||
add_tool_icon (editing_mode::minimap, tr("Minimap сreator"), font_awesome::cube);
|
add_tool_icon (editing_mode::minimap, tr("Minimap сreator"), font_awesome::images);
|
||||||
}
|
}
|
||||||
|
|
||||||
void toolbar::add_tool_icon(editing_mode mode, const QString& name, const font_awesome::icons& icon)
|
void toolbar::add_tool_icon(editing_mode mode, const QString& name, const font_awesome::icons& icon)
|
||||||
|
|||||||
@@ -33,10 +33,11 @@ namespace noggit
|
|||||||
{
|
{
|
||||||
painter->save();
|
painter->save();
|
||||||
{
|
{
|
||||||
painter->setPen((state == QIcon::On || mode == QIcon::Active)
|
QWidget* temp_btn = new QWidget();
|
||||||
? QColor(0, 0, 0)
|
|
||||||
: QColor(0, 0, 0)
|
painter->setPen(temp_btn->palette().color(QPalette::WindowText));
|
||||||
);
|
|
||||||
|
delete temp_btn;
|
||||||
|
|
||||||
if (!_fonts.count(rect.height()))
|
if (!_fonts.count(rect.height()))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
#include <QPaintEvent>
|
#include <QPaintEvent>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QToolTip>
|
#include <QToolTip>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
#include <noggit/Sky.h>
|
#include <noggit/Sky.h>
|
||||||
#include <noggit/World.h>
|
#include <noggit/World.h>
|
||||||
#include <noggit/camera.hpp>
|
#include <noggit/camera.hpp>
|
||||||
|
#include <QTransform>
|
||||||
|
|
||||||
namespace noggit
|
namespace noggit
|
||||||
{
|
{
|
||||||
@@ -21,12 +24,13 @@ namespace noggit
|
|||||||
, _draw_skies (false)
|
, _draw_skies (false)
|
||||||
{
|
{
|
||||||
setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||||
setMouseTracking(true);
|
//setMouseTracking(true);
|
||||||
|
setMaximumSize(QSize(1024, 1024));
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize minimap_widget::sizeHint() const
|
QSize minimap_widget::sizeHint() const
|
||||||
{
|
{
|
||||||
return QSize (700, 700);
|
return QSize (1024, 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \todo Only redraw stuff as told in event.
|
//! \todo Only redraw stuff as told in event.
|
||||||
@@ -45,6 +49,8 @@ namespace noggit
|
|||||||
| QPainter::SmoothPixmapTransform
|
| QPainter::SmoothPixmapTransform
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (world())
|
if (world())
|
||||||
{
|
{
|
||||||
painter.drawImage (drawing_rect, world()->horizon._qt_minimap);
|
painter.drawImage (drawing_rect, world()->horizon._qt_minimap);
|
||||||
@@ -102,6 +108,17 @@ namespace noggit
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_use_selection && _selected_tiles->at(64 * i + j))
|
||||||
|
{
|
||||||
|
painter.setPen(QColor::fromRgbF(1.0f, 0.0f, 0.0f, 1.f));
|
||||||
|
painter.drawRect ( QRect ( tile_size * i + 1
|
||||||
|
, tile_size * j + 1
|
||||||
|
, tile_size - 2
|
||||||
|
, tile_size - 2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,15 +163,27 @@ namespace noggit
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
//! \todo Draw something so user realizes this will become the minimap.
|
//! \todo Draw something so user realizes this will become the minimap.
|
||||||
painter.setPen (Qt::black);
|
painter.setPen (palette().color(QPalette::WindowText));
|
||||||
painter.setFont (QFont ("Arial", 30));
|
painter.setFont (QFont ("Arial", 30));
|
||||||
painter.drawText ( drawing_rect
|
painter.drawText ( drawing_rect
|
||||||
, Qt::AlignCenter
|
, Qt::AlignCenter
|
||||||
, tr ("Select a map on the left side.")
|
, tr ("Select a map")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPoint minimap_widget::locateTile(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
const int smaller_side ((qMin (rect().width(), rect().height()) / 64) * 64);
|
||||||
|
const int tile_size (smaller_side / 64);
|
||||||
|
//! \note event->pos() / tile_size seems to be using floating point arithmetic, therefore getting wrong results.
|
||||||
|
const QPoint tile ( event->pos().x() / float(tile_size)
|
||||||
|
, event->pos().y() / float(tile_size)
|
||||||
|
);
|
||||||
|
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
void minimap_widget::mouseDoubleClickEvent (QMouseEvent* event)
|
void minimap_widget::mouseDoubleClickEvent (QMouseEvent* event)
|
||||||
{
|
{
|
||||||
if (event->button() != Qt::LeftButton)
|
if (event->button() != Qt::LeftButton)
|
||||||
@@ -163,14 +192,7 @@ namespace noggit
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int smaller_side ((qMin (rect().width(), rect().height()) / 64) * 64);
|
QPoint tile = locateTile(event);
|
||||||
const int tile_size (smaller_side / 64);
|
|
||||||
//! \note event->pos() / tile_size seems to be using floating point arithmetic, therefore getting wrong results.
|
|
||||||
const QPoint tile ( event->pos().x() / tile_size
|
|
||||||
, event->pos().y() / tile_size
|
|
||||||
);
|
|
||||||
|
|
||||||
emit tile_clicked (tile);
|
|
||||||
|
|
||||||
if (!world()->mapIndex.hasTile (tile_index (tile.x(), tile.y())))
|
if (!world()->mapIndex.hasTile (tile_index (tile.x(), tile.y())))
|
||||||
{
|
{
|
||||||
@@ -180,24 +202,48 @@ namespace noggit
|
|||||||
|
|
||||||
event->accept();
|
event->accept();
|
||||||
|
|
||||||
emit map_clicked ( ::math::vector_3d ( (event->pos().x() / float (tile_size)) * TILESIZE
|
emit map_clicked(::math::vector_3d ( tile.x() * TILESIZE + TILESIZE / 2
|
||||||
, 0.0f
|
, 0.0f, tile.y() * TILESIZE + TILESIZE / 2));
|
||||||
, (event->pos().y() / float (tile_size)) * TILESIZE
|
}
|
||||||
)
|
|
||||||
);
|
void minimap_widget::mousePressEvent(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
if (event->button() != Qt::LeftButton)
|
||||||
|
{
|
||||||
|
event->ignore();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint tile = locateTile(event);
|
||||||
|
emit tile_clicked(tile);
|
||||||
|
_is_selecting = true;
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void minimap_widget::mouseReleaseEvent(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
_is_selecting = false;
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void minimap_widget::mouseMoveEvent(QMouseEvent* event)
|
void minimap_widget::mouseMoveEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
if (world())
|
if (world())
|
||||||
{
|
{
|
||||||
const int smaller_side((qMin(rect().width(), rect().height()) / 64) * 64);
|
QPoint tile = locateTile(event);
|
||||||
const int tile_size(smaller_side / 64);
|
|
||||||
int x = event->pos().x(), y = event->pos().y();
|
|
||||||
|
|
||||||
std::string str("ADT: " + std::to_string(x / tile_size) + "_" + std::to_string(y / tile_size));
|
std::string str("ADT: " + std::to_string(tile.x()) + "_" + std::to_string(tile.y()));
|
||||||
|
|
||||||
|
QToolTip::showText(mapToGlobal(QPoint(event->pos().x(), event->pos().y() + 5)), QString::fromStdString(str));
|
||||||
|
|
||||||
|
if (_is_selecting)
|
||||||
|
{
|
||||||
|
emit tile_clicked(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
QToolTip::showText(mapToGlobal(QPoint(x, y+5)), QString::fromStdString(str));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace math
|
namespace math
|
||||||
{
|
{
|
||||||
@@ -39,12 +40,20 @@ namespace noggit
|
|||||||
{ _draw_boundaries = draw_boundaries_; update(); return _draw_boundaries; }
|
{ _draw_boundaries = draw_boundaries_; update(); return _draw_boundaries; }
|
||||||
inline const bool& draw_boundaries() const { return _draw_boundaries; }
|
inline const bool& draw_boundaries() const { return _draw_boundaries; }
|
||||||
|
|
||||||
|
inline const std::array<bool, 4096>* use_selection (std::array<bool, 4096>* selection_)
|
||||||
|
{ _use_selection = true; _selected_tiles = selection_; update(); return _selected_tiles; }
|
||||||
|
inline const std::array<bool, 4096>* selection() const { return _selected_tiles; }
|
||||||
|
|
||||||
inline void camera (noggit::camera* camera) { _camera = camera; }
|
inline void camera (noggit::camera* camera) { _camera = camera; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void paintEvent (QPaintEvent*) override;
|
virtual void paintEvent (QPaintEvent*) override;
|
||||||
virtual void mouseDoubleClickEvent (QMouseEvent*) override;
|
virtual void mouseDoubleClickEvent (QMouseEvent*) override;
|
||||||
virtual void mouseMoveEvent(QMouseEvent*) override;
|
virtual void mouseMoveEvent(QMouseEvent*) override;
|
||||||
|
virtual void mousePressEvent(QMouseEvent* event) override;
|
||||||
|
virtual void mouseReleaseEvent(QMouseEvent* event) override;
|
||||||
|
|
||||||
|
QPoint locateTile(QMouseEvent* event);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void map_clicked (const ::math::vector_3d&);
|
void map_clicked (const ::math::vector_3d&);
|
||||||
@@ -53,9 +62,14 @@ namespace noggit
|
|||||||
private:
|
private:
|
||||||
World const* _world;
|
World const* _world;
|
||||||
noggit::camera* _camera;
|
noggit::camera* _camera;
|
||||||
|
std::array<bool, 4096>* _selected_tiles;
|
||||||
|
|
||||||
bool _draw_skies;
|
bool _draw_skies;
|
||||||
bool _draw_camera;
|
bool _draw_camera;
|
||||||
bool _draw_boundaries;
|
bool _draw_boundaries;
|
||||||
|
|
||||||
|
bool _use_selection;
|
||||||
|
bool _is_selecting = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user