2018-09-27 00:30:05 +01:00
|
|
|
#include "history.h"
|
|
|
|
#include "map.h"
|
|
|
|
#include "imageproviders.h"
|
2020-04-27 01:38:20 +01:00
|
|
|
#include "scripting.h"
|
2018-09-27 00:30:05 +01:00
|
|
|
|
2020-07-29 20:51:04 +01:00
|
|
|
#include "editcommands.h"
|
|
|
|
|
2018-09-27 00:30:05 +01:00
|
|
|
#include <QTime>
|
|
|
|
#include <QPainter>
|
|
|
|
#include <QImage>
|
|
|
|
#include <QRegularExpression>
|
|
|
|
|
|
|
|
|
|
|
|
Map::Map(QObject *parent) : QObject(parent)
|
|
|
|
{
|
2020-07-31 22:12:08 +01:00
|
|
|
editHistory.setClean();
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2020-08-07 01:39:53 +01:00
|
|
|
Map::~Map() {
|
2024-08-09 06:29:28 +01:00
|
|
|
qDeleteAll(ownedEvents);
|
|
|
|
ownedEvents.clear();
|
|
|
|
deleteConnections();
|
2020-08-07 01:39:53 +01:00
|
|
|
}
|
|
|
|
|
2018-09-27 00:30:05 +01:00
|
|
|
void Map::setName(QString mapName) {
|
|
|
|
name = mapName;
|
|
|
|
constantName = mapConstantFromName(mapName);
|
2024-01-29 19:07:13 +00:00
|
|
|
scriptsLoaded = false;
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2023-12-18 07:19:12 +00:00
|
|
|
QString Map::mapConstantFromName(QString mapName, bool includePrefix) {
|
2018-09-27 00:30:05 +01:00
|
|
|
// Transform map names of the form 'GraniteCave_B1F` into map constants like 'MAP_GRANITE_CAVE_B1F'.
|
2022-11-23 03:57:26 +00:00
|
|
|
static const QRegularExpression caseChange("([a-z])([A-Z])");
|
|
|
|
QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2");
|
2023-12-18 07:19:12 +00:00
|
|
|
const QString prefix = includePrefix ? projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) : "";
|
|
|
|
QString withMapAndUppercase = prefix + nameWithUnderscores.toUpper();
|
2022-11-23 03:57:26 +00:00
|
|
|
static const QRegularExpression underscores("_+");
|
|
|
|
QString constantName = withMapAndUppercase.replace(underscores, "_");
|
2018-09-27 00:30:05 +01:00
|
|
|
|
|
|
|
// Handle special cases.
|
|
|
|
// SSTidal needs to be SS_TIDAL, rather than SSTIDAL
|
|
|
|
constantName = constantName.replace("SSTIDAL", "SS_TIDAL");
|
|
|
|
|
|
|
|
return constantName;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Map::getWidth() {
|
2021-07-20 03:02:31 +01:00
|
|
|
return layout->getWidth();
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int Map::getHeight() {
|
2021-07-20 03:02:31 +01:00
|
|
|
return layout->getHeight();
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2020-03-13 06:23:47 +00:00
|
|
|
int Map::getBorderWidth() {
|
2021-07-20 03:02:31 +01:00
|
|
|
return layout->getBorderWidth();
|
2020-03-13 06:23:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int Map::getBorderHeight() {
|
2021-07-20 03:02:31 +01:00
|
|
|
return layout->getBorderHeight();
|
2020-03-13 06:23:47 +00:00
|
|
|
}
|
|
|
|
|
2021-02-14 21:56:23 +00:00
|
|
|
bool Map::mapBlockChanged(int i, const Blockdata &cache) {
|
2021-02-14 21:34:17 +00:00
|
|
|
if (cache.length() <= i)
|
2018-09-27 00:30:05 +01:00
|
|
|
return true;
|
2021-02-14 21:34:17 +00:00
|
|
|
if (layout->blockdata.length() <= i)
|
2018-09-27 00:30:05 +01:00
|
|
|
return true;
|
|
|
|
|
2021-02-14 21:34:17 +00:00
|
|
|
return layout->blockdata.at(i) != cache.at(i);
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2021-02-14 21:56:23 +00:00
|
|
|
bool Map::borderBlockChanged(int i, const Blockdata &cache) {
|
2021-02-14 21:34:17 +00:00
|
|
|
if (cache.length() <= i)
|
2020-03-14 07:44:55 +00:00
|
|
|
return true;
|
2021-02-14 21:34:17 +00:00
|
|
|
if (layout->border.length() <= i)
|
2020-03-14 07:44:55 +00:00
|
|
|
return true;
|
|
|
|
|
2021-02-14 21:34:17 +00:00
|
|
|
return layout->border.at(i) != cache.at(i);
|
2020-03-14 07:44:55 +00:00
|
|
|
}
|
|
|
|
|
2023-01-17 01:16:25 +00:00
|
|
|
void Map::clearBorderCache() {
|
|
|
|
layout->cached_border.clear();
|
|
|
|
}
|
|
|
|
|
2018-09-27 00:30:05 +01:00
|
|
|
void Map::cacheBorder() {
|
2021-02-14 21:34:17 +00:00
|
|
|
layout->cached_border.clear();
|
|
|
|
for (const auto &block : layout->border)
|
|
|
|
layout->cached_border.append(block);
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Map::cacheBlockdata() {
|
2021-02-14 21:34:17 +00:00
|
|
|
layout->cached_blockdata.clear();
|
|
|
|
for (const auto &block : layout->blockdata)
|
|
|
|
layout->cached_blockdata.append(block);
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Map::cacheCollision() {
|
2021-02-14 21:34:17 +00:00
|
|
|
layout->cached_collision.clear();
|
|
|
|
for (const auto &block : layout->blockdata)
|
|
|
|
layout->cached_collision.append(block);
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2023-01-07 02:57:42 +00:00
|
|
|
QPixmap Map::renderCollision(bool ignoreCache) {
|
2018-09-27 00:30:05 +01:00
|
|
|
bool changed_any = false;
|
|
|
|
int width_ = getWidth();
|
|
|
|
int height_ = getHeight();
|
2021-02-14 21:34:17 +00:00
|
|
|
if (collision_image.isNull() || collision_image.width() != width_ * 16 || collision_image.height() != height_ * 16) {
|
2018-09-27 00:30:05 +01:00
|
|
|
collision_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888);
|
|
|
|
changed_any = true;
|
|
|
|
}
|
2021-02-14 21:34:17 +00:00
|
|
|
if (layout->blockdata.isEmpty() || !width_ || !height_) {
|
2018-09-27 00:30:05 +01:00
|
|
|
collision_pixmap = collision_pixmap.fromImage(collision_image);
|
|
|
|
return collision_pixmap;
|
|
|
|
}
|
|
|
|
QPainter painter(&collision_image);
|
2021-02-14 21:34:17 +00:00
|
|
|
for (int i = 0; i < layout->blockdata.length(); i++) {
|
|
|
|
if (!ignoreCache && !mapBlockChanged(i, layout->cached_collision)) {
|
2018-09-27 00:30:05 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
changed_any = true;
|
2021-02-14 21:34:17 +00:00
|
|
|
Block block = layout->blockdata.at(i);
|
2018-09-27 00:30:05 +01:00
|
|
|
QImage collision_metatile_image = getCollisionMetatileImage(block);
|
|
|
|
int map_y = width_ ? i / width_ : 0;
|
|
|
|
int map_x = width_ ? i % width_ : 0;
|
|
|
|
QPoint metatile_origin = QPoint(map_x * 16, map_y * 16);
|
|
|
|
painter.drawImage(metatile_origin, collision_metatile_image);
|
|
|
|
}
|
|
|
|
painter.end();
|
|
|
|
cacheCollision();
|
|
|
|
if (changed_any) {
|
|
|
|
collision_pixmap = collision_pixmap.fromImage(collision_image);
|
|
|
|
}
|
|
|
|
return collision_pixmap;
|
|
|
|
}
|
|
|
|
|
2023-01-07 02:57:42 +00:00
|
|
|
QPixmap Map::render(bool ignoreCache, MapLayout * fromLayout, QRect bounds) {
|
2018-09-27 00:30:05 +01:00
|
|
|
bool changed_any = false;
|
|
|
|
int width_ = getWidth();
|
|
|
|
int height_ = getHeight();
|
2021-02-14 21:34:17 +00:00
|
|
|
if (image.isNull() || image.width() != width_ * 16 || image.height() != height_ * 16) {
|
2018-09-27 00:30:05 +01:00
|
|
|
image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888);
|
|
|
|
changed_any = true;
|
|
|
|
}
|
2021-02-14 21:34:17 +00:00
|
|
|
if (layout->blockdata.isEmpty() || !width_ || !height_) {
|
2018-09-27 00:30:05 +01:00
|
|
|
pixmap = pixmap.fromImage(image);
|
|
|
|
return pixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
QPainter painter(&image);
|
2021-02-14 21:34:17 +00:00
|
|
|
for (int i = 0; i < layout->blockdata.length(); i++) {
|
2020-03-14 07:44:55 +00:00
|
|
|
if (!ignoreCache && !mapBlockChanged(i, layout->cached_blockdata)) {
|
2018-09-27 00:30:05 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
changed_any = true;
|
2023-01-06 21:17:42 +00:00
|
|
|
int map_y = width_ ? i / width_ : 0;
|
|
|
|
int map_x = width_ ? i % width_ : 0;
|
|
|
|
if (bounds.isValid() && !bounds.contains(map_x, map_y)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
QPoint metatile_origin = QPoint(map_x * 16, map_y * 16);
|
2021-02-14 21:34:17 +00:00
|
|
|
Block block = layout->blockdata.at(i);
|
2019-11-04 23:44:57 +00:00
|
|
|
QImage metatile_image = getMetatileImage(
|
2023-12-12 23:32:54 +00:00
|
|
|
block.metatileId(),
|
2019-11-04 23:44:57 +00:00
|
|
|
fromLayout ? fromLayout->tileset_primary : layout->tileset_primary,
|
2020-07-02 02:19:08 +01:00
|
|
|
fromLayout ? fromLayout->tileset_secondary : layout->tileset_secondary,
|
2020-07-02 02:43:19 +01:00
|
|
|
metatileLayerOrder,
|
|
|
|
metatileLayerOpacity
|
2019-11-04 23:44:57 +00:00
|
|
|
);
|
2018-09-27 00:30:05 +01:00
|
|
|
painter.drawImage(metatile_origin, metatile_image);
|
|
|
|
}
|
|
|
|
painter.end();
|
|
|
|
if (changed_any) {
|
|
|
|
cacheBlockdata();
|
|
|
|
pixmap = pixmap.fromImage(image);
|
|
|
|
}
|
|
|
|
|
|
|
|
return pixmap;
|
|
|
|
}
|
|
|
|
|
2020-05-03 16:00:56 +01:00
|
|
|
QPixmap Map::renderBorder(bool ignoreCache) {
|
2020-03-14 07:44:55 +00:00
|
|
|
bool changed_any = false, border_resized = false;
|
2020-03-13 06:23:47 +00:00
|
|
|
int width_ = getBorderWidth();
|
|
|
|
int height_ = getBorderHeight();
|
2018-09-27 00:30:05 +01:00
|
|
|
if (layout->border_image.isNull()) {
|
|
|
|
layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888);
|
|
|
|
changed_any = true;
|
|
|
|
}
|
2020-03-14 07:44:55 +00:00
|
|
|
if (layout->border_image.width() != width_ * 16 || layout->border_image.height() != height_ * 16) {
|
|
|
|
layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888);
|
|
|
|
border_resized = true;
|
|
|
|
}
|
2021-02-14 21:34:17 +00:00
|
|
|
if (layout->border.isEmpty()) {
|
2018-09-27 00:30:05 +01:00
|
|
|
layout->border_pixmap = layout->border_pixmap.fromImage(layout->border_image);
|
|
|
|
return layout->border_pixmap;
|
|
|
|
}
|
|
|
|
QPainter painter(&layout->border_image);
|
2021-02-14 21:34:17 +00:00
|
|
|
for (int i = 0; i < layout->border.length(); i++) {
|
2020-05-03 16:00:56 +01:00
|
|
|
if (!ignoreCache && (!border_resized && !borderBlockChanged(i, layout->cached_border))) {
|
2018-09-27 00:30:05 +01:00
|
|
|
continue;
|
|
|
|
}
|
2020-03-14 07:44:55 +00:00
|
|
|
|
2018-09-27 00:30:05 +01:00
|
|
|
changed_any = true;
|
2021-02-14 21:34:17 +00:00
|
|
|
Block block = layout->border.at(i);
|
2023-12-12 23:32:54 +00:00
|
|
|
uint16_t metatileId = block.metatileId();
|
2021-11-24 04:53:41 +00:00
|
|
|
QImage metatile_image = getMetatileImage(metatileId, layout->tileset_primary, layout->tileset_secondary, metatileLayerOrder, metatileLayerOpacity);
|
2020-03-14 07:44:55 +00:00
|
|
|
int map_y = width_ ? i / width_ : 0;
|
|
|
|
int map_x = width_ ? i % width_ : 0;
|
2018-09-27 00:30:05 +01:00
|
|
|
painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image);
|
|
|
|
}
|
|
|
|
painter.end();
|
|
|
|
if (changed_any) {
|
|
|
|
cacheBorder();
|
|
|
|
layout->border_pixmap = layout->border_pixmap.fromImage(layout->border_image);
|
|
|
|
}
|
|
|
|
return layout->border_pixmap;
|
|
|
|
}
|
|
|
|
|
2024-08-15 03:22:54 +01:00
|
|
|
// Get the portion of the map that can be rendered when rendered as a map connection.
|
|
|
|
// Cardinal connections render the nearest segment of their map and within the bounds of the border draw distance,
|
|
|
|
// Dive/Emerge connections are rendered normally within the bounds of their parent map.
|
|
|
|
QRect Map::getConnectionRect(const QString &direction, MapLayout * fromLayout) {
|
|
|
|
int x = 0, y = 0;
|
|
|
|
int w = getWidth(), h = getHeight();
|
|
|
|
|
2024-08-04 21:08:16 +01:00
|
|
|
if (direction == "up") {
|
2024-08-14 03:18:40 +01:00
|
|
|
h = qMin(h, BORDER_DISTANCE);
|
|
|
|
y = getHeight() - h;
|
2024-08-04 21:08:16 +01:00
|
|
|
} else if (direction == "down") {
|
2024-08-14 03:18:40 +01:00
|
|
|
h = qMin(h, BORDER_DISTANCE);
|
2024-08-04 21:08:16 +01:00
|
|
|
} else if (direction == "left") {
|
2024-08-14 03:18:40 +01:00
|
|
|
w = qMin(w, BORDER_DISTANCE);
|
|
|
|
x = getWidth() - w;
|
2024-08-04 21:08:16 +01:00
|
|
|
} else if (direction == "right") {
|
2024-08-14 03:18:40 +01:00
|
|
|
w = qMin(w, BORDER_DISTANCE);
|
|
|
|
} else if (MapConnection::isDiving(direction)) {
|
|
|
|
if (fromLayout) {
|
|
|
|
w = qMin(w, fromLayout->getWidth());
|
|
|
|
h = qMin(h, fromLayout->getHeight());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Unknown direction
|
2024-08-15 03:22:54 +01:00
|
|
|
return QRect();
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
2024-08-15 03:22:54 +01:00
|
|
|
return QRect(x, y, w, h);
|
|
|
|
}
|
2023-01-06 21:17:42 +00:00
|
|
|
|
2024-08-15 03:22:54 +01:00
|
|
|
QPixmap Map::renderConnection(const QString &direction, MapLayout * fromLayout) {
|
|
|
|
QRect bounds = getConnectionRect(direction, fromLayout);
|
|
|
|
if (!bounds.isValid())
|
|
|
|
return QPixmap();
|
|
|
|
|
|
|
|
// 'fromLayout' will be used in 'render' to get the palettes from the parent map.
|
|
|
|
// Dive/Emerge connections render normally with their own palettes, so we ignore this.
|
|
|
|
if (MapConnection::isDiving(direction))
|
|
|
|
fromLayout = nullptr;
|
|
|
|
|
|
|
|
render(true, fromLayout, bounds);
|
|
|
|
QImage connection_image = image.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16);
|
2018-09-27 00:30:05 +01:00
|
|
|
return QPixmap::fromImage(connection_image);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Map::setNewDimensionsBlockdata(int newWidth, int newHeight) {
|
|
|
|
int oldWidth = getWidth();
|
|
|
|
int oldHeight = getHeight();
|
|
|
|
|
2021-02-14 23:14:04 +00:00
|
|
|
Blockdata newBlockdata;
|
2018-09-27 00:30:05 +01:00
|
|
|
|
|
|
|
for (int y = 0; y < newHeight; y++)
|
|
|
|
for (int x = 0; x < newWidth; x++) {
|
|
|
|
if (x < oldWidth && y < oldHeight) {
|
|
|
|
int index = y * oldWidth + x;
|
2021-02-14 23:14:04 +00:00
|
|
|
newBlockdata.append(layout->blockdata.value(index));
|
2018-09-27 00:30:05 +01:00
|
|
|
} else {
|
2021-02-14 23:14:04 +00:00
|
|
|
newBlockdata.append(0);
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
}
|
2021-02-14 23:14:04 +00:00
|
|
|
|
|
|
|
layout->blockdata = newBlockdata;
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2020-03-14 07:44:55 +00:00
|
|
|
void Map::setNewBorderDimensionsBlockdata(int newWidth, int newHeight) {
|
|
|
|
int oldWidth = getBorderWidth();
|
|
|
|
int oldHeight = getBorderHeight();
|
|
|
|
|
2021-02-14 23:14:04 +00:00
|
|
|
Blockdata newBlockdata;
|
2020-03-14 07:44:55 +00:00
|
|
|
|
|
|
|
for (int y = 0; y < newHeight; y++)
|
|
|
|
for (int x = 0; x < newWidth; x++) {
|
|
|
|
if (x < oldWidth && y < oldHeight) {
|
|
|
|
int index = y * oldWidth + x;
|
2021-02-14 23:14:04 +00:00
|
|
|
newBlockdata.append(layout->border.value(index));
|
2020-03-14 07:44:55 +00:00
|
|
|
} else {
|
2021-02-14 23:14:04 +00:00
|
|
|
newBlockdata.append(0);
|
2020-03-14 07:44:55 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-14 23:14:04 +00:00
|
|
|
|
|
|
|
layout->border = newBlockdata;
|
2020-03-14 07:44:55 +00:00
|
|
|
}
|
|
|
|
|
2022-10-12 03:50:08 +01:00
|
|
|
void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) {
|
2018-09-27 00:30:05 +01:00
|
|
|
if (setNewBlockdata) {
|
|
|
|
setNewDimensionsBlockdata(newWidth, newHeight);
|
|
|
|
}
|
|
|
|
|
2022-10-16 07:49:42 +01:00
|
|
|
int oldWidth = layout->width;
|
|
|
|
int oldHeight = layout->height;
|
|
|
|
layout->width = newWidth;
|
|
|
|
layout->height = newHeight;
|
2021-12-26 16:52:15 +00:00
|
|
|
|
2022-10-12 03:50:08 +01:00
|
|
|
if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) {
|
2021-12-09 22:50:01 +00:00
|
|
|
Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight);
|
|
|
|
}
|
|
|
|
|
2020-10-24 12:45:08 +01:00
|
|
|
emit mapDimensionsChanged(QSize(getWidth(), getHeight()));
|
2024-07-08 21:01:30 +01:00
|
|
|
modify();
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2022-10-12 03:50:08 +01:00
|
|
|
void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) {
|
2020-03-14 07:44:55 +00:00
|
|
|
if (setNewBlockdata) {
|
|
|
|
setNewBorderDimensionsBlockdata(newWidth, newHeight);
|
|
|
|
}
|
|
|
|
|
2022-10-16 07:49:42 +01:00
|
|
|
int oldWidth = layout->border_width;
|
|
|
|
int oldHeight = layout->border_height;
|
|
|
|
layout->border_width = newWidth;
|
|
|
|
layout->border_height = newHeight;
|
2020-03-14 07:44:55 +00:00
|
|
|
|
2022-10-12 03:50:08 +01:00
|
|
|
if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) {
|
2022-08-29 17:44:17 +01:00
|
|
|
Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight);
|
|
|
|
}
|
|
|
|
|
2024-07-08 21:01:30 +01:00
|
|
|
modify();
|
2020-03-14 07:44:55 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 23:53:07 +01:00
|
|
|
void Map::openScript(QString label) {
|
|
|
|
emit openScriptRequested(label);
|
|
|
|
}
|
|
|
|
|
2021-02-13 21:16:52 +00:00
|
|
|
bool Map::getBlock(int x, int y, Block *out) {
|
2022-02-08 22:40:07 +00:00
|
|
|
if (isWithinBounds(x, y)) {
|
2021-02-14 21:34:17 +00:00
|
|
|
int i = y * getWidth() + x;
|
|
|
|
*out = layout->blockdata.value(i);
|
|
|
|
return true;
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
2021-02-13 21:16:52 +00:00
|
|
|
return false;
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2020-04-30 02:41:19 +01:00
|
|
|
void Map::setBlock(int x, int y, Block block, bool enableScriptCallback) {
|
2022-08-29 18:14:06 +01:00
|
|
|
if (!isWithinBounds(x, y)) return;
|
2018-09-27 00:30:05 +01:00
|
|
|
int i = y * getWidth() + x;
|
2021-02-14 21:34:17 +00:00
|
|
|
if (i < layout->blockdata.size()) {
|
|
|
|
Block prevBlock = layout->blockdata.at(i);
|
|
|
|
layout->blockdata.replace(i, block);
|
2020-04-30 02:41:19 +01:00
|
|
|
if (enableScriptCallback) {
|
2020-04-27 01:38:20 +01:00
|
|
|
Scripting::cb_MetatileChanged(x, y, prevBlock, block);
|
|
|
|
}
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-12 03:50:08 +01:00
|
|
|
void Map::setBlockdata(Blockdata blockdata, bool enableScriptCallback) {
|
2021-12-10 03:14:54 +00:00
|
|
|
int width = getWidth();
|
|
|
|
int size = qMin(blockdata.size(), layout->blockdata.size());
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
|
|
Block prevBlock = layout->blockdata.at(i);
|
|
|
|
Block newBlock = blockdata.at(i);
|
|
|
|
if (prevBlock != newBlock) {
|
|
|
|
layout->blockdata.replace(i, newBlock);
|
2022-10-12 03:50:08 +01:00
|
|
|
if (enableScriptCallback)
|
|
|
|
Scripting::cb_MetatileChanged(i % width, i / width, prevBlock, newBlock);
|
2021-12-10 03:14:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-29 17:44:17 +01:00
|
|
|
uint16_t Map::getBorderMetatileId(int x, int y) {
|
|
|
|
int i = y * getBorderWidth() + x;
|
2023-12-12 23:32:54 +00:00
|
|
|
return layout->border[i].metatileId();
|
2022-08-29 17:44:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Map::setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback) {
|
|
|
|
int i = y * getBorderWidth() + x;
|
|
|
|
if (i < layout->border.size()) {
|
2023-12-12 23:32:54 +00:00
|
|
|
uint16_t prevMetatileId = layout->border[i].metatileId();
|
|
|
|
layout->border[i].setMetatileId(metatileId);
|
2022-08-29 17:44:17 +01:00
|
|
|
if (prevMetatileId != metatileId && enableScriptCallback) {
|
|
|
|
Scripting::cb_BorderMetatileChanged(x, y, prevMetatileId, metatileId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-12 03:50:08 +01:00
|
|
|
void Map::setBorderBlockData(Blockdata blockdata, bool enableScriptCallback) {
|
2022-08-29 17:44:17 +01:00
|
|
|
int width = getBorderWidth();
|
|
|
|
int size = qMin(blockdata.size(), layout->border.size());
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
|
|
Block prevBlock = layout->border.at(i);
|
|
|
|
Block newBlock = blockdata.at(i);
|
|
|
|
if (prevBlock != newBlock) {
|
|
|
|
layout->border.replace(i, newBlock);
|
2022-10-12 03:50:08 +01:00
|
|
|
if (enableScriptCallback)
|
2023-12-12 23:32:54 +00:00
|
|
|
Scripting::cb_BorderMetatileChanged(i % width, i / width, prevBlock.metatileId(), newBlock.metatileId());
|
2022-08-29 17:44:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-27 00:30:05 +01:00
|
|
|
void Map::_floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) {
|
|
|
|
QList<QPoint> todo;
|
|
|
|
todo.append(QPoint(x, y));
|
|
|
|
while (todo.length()) {
|
2021-02-14 21:34:17 +00:00
|
|
|
QPoint point = todo.takeAt(0);
|
|
|
|
x = point.x();
|
|
|
|
y = point.y();
|
|
|
|
Block block;
|
|
|
|
if (!getBlock(x, y, &block)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-12-12 23:32:54 +00:00
|
|
|
uint old_coll = block.collision();
|
|
|
|
uint old_elev = block.elevation();
|
2021-02-14 21:34:17 +00:00
|
|
|
if (old_coll == collision && old_elev == elevation) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-12-12 23:32:54 +00:00
|
|
|
block.setCollision(collision);
|
|
|
|
block.setElevation(elevation);
|
2021-02-14 21:34:17 +00:00
|
|
|
setBlock(x, y, block, true);
|
2023-12-12 23:32:54 +00:00
|
|
|
if (getBlock(x + 1, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) {
|
2021-02-14 21:34:17 +00:00
|
|
|
todo.append(QPoint(x + 1, y));
|
|
|
|
}
|
2023-12-12 23:32:54 +00:00
|
|
|
if (getBlock(x - 1, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) {
|
2021-02-14 21:34:17 +00:00
|
|
|
todo.append(QPoint(x - 1, y));
|
|
|
|
}
|
2023-12-12 23:32:54 +00:00
|
|
|
if (getBlock(x, y + 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) {
|
2021-02-14 21:34:17 +00:00
|
|
|
todo.append(QPoint(x, y + 1));
|
|
|
|
}
|
2023-12-12 23:32:54 +00:00
|
|
|
if (getBlock(x, y - 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) {
|
2021-02-14 21:34:17 +00:00
|
|
|
todo.append(QPoint(x, y - 1));
|
|
|
|
}
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Map::floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) {
|
2021-02-14 21:34:17 +00:00
|
|
|
Block block;
|
2023-12-12 23:32:54 +00:00
|
|
|
if (getBlock(x, y, &block) && (block.collision() != collision || block.elevation() != elevation)) {
|
2018-09-27 00:30:05 +01:00
|
|
|
_floodFillCollisionElevation(x, y, collision, elevation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-05 02:49:21 +00:00
|
|
|
void Map::magicFillCollisionElevation(int initialX, int initialY, uint16_t collision, uint16_t elevation) {
|
2021-02-13 21:16:52 +00:00
|
|
|
Block block;
|
2023-12-12 23:32:54 +00:00
|
|
|
if (getBlock(initialX, initialY, &block) && (block.collision() != collision || block.elevation() != elevation)) {
|
|
|
|
uint old_coll = block.collision();
|
|
|
|
uint old_elev = block.elevation();
|
2019-01-05 02:49:21 +00:00
|
|
|
|
|
|
|
for (int y = 0; y < getHeight(); y++) {
|
|
|
|
for (int x = 0; x < getWidth(); x++) {
|
2023-12-12 23:32:54 +00:00
|
|
|
if (getBlock(x, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) {
|
|
|
|
block.setCollision(collision);
|
|
|
|
block.setElevation(elevation);
|
2021-02-13 21:16:52 +00:00
|
|
|
setBlock(x, y, block, true);
|
2019-01-05 02:49:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-01 21:32:08 +00:00
|
|
|
QList<Event *> Map::getAllEvents() const {
|
|
|
|
QList<Event *> all_events;
|
|
|
|
for (const auto &event_list : events) {
|
|
|
|
all_events << event_list;
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
2020-12-01 21:32:08 +00:00
|
|
|
return all_events;
|
|
|
|
}
|
|
|
|
|
2024-01-29 19:07:13 +00:00
|
|
|
QStringList Map::getScriptLabels(Event::Group group) {
|
|
|
|
if (!this->scriptsLoaded) {
|
|
|
|
this->scriptsFileLabels = ParseUtil::getGlobalScriptLabels(this->getScriptsFilePath());
|
|
|
|
this->scriptsLoaded = true;
|
|
|
|
}
|
|
|
|
|
2020-12-01 21:32:08 +00:00
|
|
|
QStringList scriptLabels;
|
2022-07-19 22:56:12 +01:00
|
|
|
|
2023-12-30 02:54:37 +00:00
|
|
|
// Get script labels currently in-use by the map's events
|
2022-07-19 22:56:12 +01:00
|
|
|
if (group == Event::Group::None) {
|
|
|
|
ScriptTracker scriptTracker;
|
|
|
|
for (Event *event : this->getAllEvents()) {
|
|
|
|
event->accept(&scriptTracker);
|
|
|
|
}
|
|
|
|
scriptLabels = scriptTracker.getScripts();
|
2020-12-01 21:32:08 +00:00
|
|
|
} else {
|
2022-07-19 22:56:12 +01:00
|
|
|
ScriptTracker scriptTracker;
|
|
|
|
for (Event *event : events.value(group)) {
|
|
|
|
event->accept(&scriptTracker);
|
|
|
|
}
|
|
|
|
scriptLabels = scriptTracker.getScripts();
|
2020-12-01 21:32:08 +00:00
|
|
|
}
|
|
|
|
|
2023-12-30 02:54:37 +00:00
|
|
|
// Add scripts from map's scripts file, and empty names.
|
2024-01-29 19:07:13 +00:00
|
|
|
scriptLabels.append(this->scriptsFileLabels);
|
2024-01-05 17:45:16 +00:00
|
|
|
scriptLabels.sort(Qt::CaseInsensitive);
|
2022-07-19 22:56:12 +01:00
|
|
|
scriptLabels.prepend("0x0");
|
|
|
|
scriptLabels.prepend("NULL");
|
2023-12-30 02:54:37 +00:00
|
|
|
|
|
|
|
scriptLabels.removeAll("");
|
2023-09-20 00:08:17 +01:00
|
|
|
scriptLabels.removeDuplicates();
|
2020-12-01 21:32:08 +00:00
|
|
|
|
|
|
|
return scriptLabels;
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2023-12-30 02:54:37 +00:00
|
|
|
QString Map::getScriptsFilePath() const {
|
2024-07-15 21:14:38 +01:00
|
|
|
const bool usePoryscript = projectConfig.usePoryScript;
|
2023-12-30 02:54:37 +00:00
|
|
|
auto path = QDir::cleanPath(QString("%1/%2/%3/scripts")
|
2024-07-15 21:14:38 +01:00
|
|
|
.arg(projectConfig.projectDir)
|
2023-12-30 02:54:37 +00:00
|
|
|
.arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders))
|
|
|
|
.arg(this->name));
|
|
|
|
auto extension = Project::getScriptFileExtension(usePoryscript);
|
|
|
|
if (usePoryscript && !QFile::exists(path + extension))
|
|
|
|
extension = Project::getScriptFileExtension(false);
|
|
|
|
path += extension;
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2018-09-27 00:30:05 +01:00
|
|
|
void Map::removeEvent(Event *event) {
|
2022-07-19 22:56:12 +01:00
|
|
|
for (Event::Group key : events.keys()) {
|
2018-09-27 00:30:05 +01:00
|
|
|
events[key].removeAll(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Map::addEvent(Event *event) {
|
2022-07-19 22:56:12 +01:00
|
|
|
event->setMap(this);
|
|
|
|
events[event->getEventGroup()].append(event);
|
2020-08-07 01:39:53 +01:00
|
|
|
if (!ownedEvents.contains(event)) ownedEvents.append(event);
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
|
|
|
|
2024-08-09 06:29:28 +01:00
|
|
|
void Map::deleteConnections() {
|
2024-08-15 03:22:54 +01:00
|
|
|
qDeleteAll(this->ownedConnections);
|
|
|
|
this->ownedConnections.clear();
|
|
|
|
this->connections.clear();
|
2024-08-09 06:29:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QList<MapConnection*> Map::getConnections() const {
|
|
|
|
return connections;
|
2024-08-05 00:11:29 +01:00
|
|
|
}
|
|
|
|
|
2024-08-15 03:22:54 +01:00
|
|
|
bool Map::addConnection(MapConnection *connection) {
|
|
|
|
if (!connection || this->connections.contains(connection))
|
|
|
|
return false;
|
2024-08-09 06:29:28 +01:00
|
|
|
|
2024-08-15 03:22:54 +01:00
|
|
|
// Maps should only have one Dive/Emerge connection at a time.
|
|
|
|
// (Users can technically have more by editing their data manually, but we will only display one at a time)
|
|
|
|
// Any additional connections being added (this can happen via mirroring) are tracked for deleting but otherwise ignored.
|
2024-08-09 06:29:28 +01:00
|
|
|
if (MapConnection::isDiving(connection->direction())) {
|
2024-08-15 03:22:54 +01:00
|
|
|
for (auto i : this->connections) {
|
|
|
|
if (i->direction() == connection->direction()) {
|
|
|
|
this->ownedConnections.insert(connection);
|
|
|
|
connection->setParentMap(this, false);
|
|
|
|
return true;
|
2024-08-09 06:29:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loadConnection(connection);
|
2024-08-05 00:11:29 +01:00
|
|
|
modify();
|
|
|
|
emit connectionAdded(connection);
|
2024-08-15 03:22:54 +01:00
|
|
|
return true;
|
2024-08-05 00:11:29 +01:00
|
|
|
}
|
|
|
|
|
2024-08-09 06:29:28 +01:00
|
|
|
void Map::loadConnection(MapConnection *connection) {
|
|
|
|
if (!connection)
|
|
|
|
return;
|
2024-08-15 03:22:54 +01:00
|
|
|
this->connections.append(connection);
|
|
|
|
this->ownedConnections.insert(connection);
|
2024-08-09 06:29:28 +01:00
|
|
|
connection->setParentMap(this, false);
|
|
|
|
}
|
|
|
|
|
2024-08-15 03:22:54 +01:00
|
|
|
// connection should not be deleted here, a pointer to it is allowed to persist in the edit history
|
|
|
|
bool Map::removeConnection(MapConnection *connection) {
|
|
|
|
if (!this->connections.removeOne(connection))
|
|
|
|
return false;
|
2024-08-09 06:29:28 +01:00
|
|
|
modify();
|
|
|
|
emit connectionRemoved(connection);
|
2024-08-15 03:22:54 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Same as Map::removeConnection but caller takes ownership of connection.
|
|
|
|
bool Map::takeConnection(MapConnection *connection) {
|
|
|
|
if (!this->removeConnection(connection))
|
|
|
|
return false;
|
|
|
|
connection->setParentMap(nullptr, false);
|
|
|
|
this->ownedConnections.remove(connection);
|
|
|
|
return true;
|
2024-08-09 06:29:28 +01:00
|
|
|
}
|
|
|
|
|
2022-09-28 05:21:01 +01:00
|
|
|
void Map::modify() {
|
|
|
|
emit modified();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Map::clean() {
|
|
|
|
this->hasUnsavedDataChanges = false;
|
|
|
|
}
|
|
|
|
|
2018-09-27 00:30:05 +01:00
|
|
|
bool Map::hasUnsavedChanges() {
|
2021-09-08 03:40:07 +01:00
|
|
|
return !editHistory.isClean() || hasUnsavedDataChanges || !isPersistedToFile;
|
2018-09-27 00:30:05 +01:00
|
|
|
}
|
2022-02-08 22:40:07 +00:00
|
|
|
|
2024-08-15 03:22:54 +01:00
|
|
|
void Map::pruneEditHistory() {
|
|
|
|
// Edit history for map connections gets messy because edits on other maps can affect the current map.
|
|
|
|
// To avoid complications we clear MapConnection edit history when the user opens a different map.
|
|
|
|
// No other edits within a single map depend on MapConnections so they can be pruned safely.
|
|
|
|
static const QSet<int> mapConnectionIds = {
|
|
|
|
ID_MapConnectionMove,
|
|
|
|
ID_MapConnectionChangeDirection,
|
|
|
|
ID_MapConnectionChangeMap,
|
|
|
|
ID_MapConnectionAdd,
|
|
|
|
ID_MapConnectionRemove
|
|
|
|
};
|
|
|
|
for (int i = 0; i < this->editHistory.count(); i++) {
|
|
|
|
// Qt really doesn't expect editing commands in the stack to be valid (fair).
|
|
|
|
// A better future design might be to have separate edit histories per map tab,
|
|
|
|
// and dumping the entire Connections tab history with QUndoStack::clear.
|
|
|
|
auto command = const_cast<QUndoCommand*>(this->editHistory.command(i));
|
|
|
|
if (mapConnectionIds.contains(command->id()))
|
|
|
|
command->setObsolete(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-08 22:40:07 +00:00
|
|
|
bool Map::isWithinBounds(int x, int y) {
|
|
|
|
return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight());
|
|
|
|
}
|
2022-08-29 17:44:17 +01:00
|
|
|
|
|
|
|
bool Map::isWithinBorderBounds(int x, int y) {
|
|
|
|
return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight());
|
|
|
|
}
|