mirror of
https://github.com/Mudlet/Mudlet
synced 2026-08-13 18:26:27 -04:00
#### Brief overview of PR changes/additions Saving a map at format <= 19 was leaking internal `system.fallback_*` keys into the live map's user-visible userData, permanently. A format-19 save polluted room userData; a save at format < 19 tainted map userData forever. - The first commit serialises a locally-augmented copy, so saves never mutate the live map. Old-format **file** output stays byte-identical, so older Mudlets still receive their compatibility keys. - The second commit strips stale leaked keys on format >= 19 loads, so already-tainted maps self-clean. #### Motivation for adding to Mudlet Users' maps were silently accumulating internal keys they never set, and those keys survived across save/load cycles. This stops new pollution and cleans up existing damage. #### Other info (issues closed, discussion etc) Stacked on top of #9468 (base branch `add-persistence-roundtrip-tests`); GitHub will auto-retarget to `development` once that PR merges. Please merge after #9468. These are the two bugs the round-trip tests caught, so this PR also flips their `QEXPECT_FAIL` markers to hard assertions. Human build/test pending; DCO sign-off to be added at squash time. https://github.com/user-attachments/assets/3931edb7-4daf-4277-bac4-0c6680d5bfe3
2488 lines
92 KiB
C++
2488 lines
92 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2012-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
|
|
* Copyright (C) 2014 by Ahmed Charles - acharles@outlook.com *
|
|
* Copyright (C) 2014-2016, 2018, 2020-2021, 2023 by Stephen Lyons *
|
|
* - slysven@virginmedia.com *
|
|
* Copyright (C) 2025 by Lecker Kebap - Leris@mudlet.org *
|
|
* *
|
|
* This program 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 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program 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, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
***************************************************************************/
|
|
|
|
|
|
#include "TRoom.h"
|
|
|
|
|
|
#include "TArea.h"
|
|
#include "TMap.h"
|
|
#include "TRoomDB.h"
|
|
#include "mudlet.h"
|
|
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include <QJsonValue>
|
|
#include <QString>
|
|
#include <QStringBuilder>
|
|
|
|
|
|
// Helper needed to allow Qt::PenStyle enum to be unserialised (read from file)
|
|
// in Qt5 - the compilation errors that result in not having this are really
|
|
// confusing!
|
|
QDataStream& operator>>(QDataStream& ds, Qt::PenStyle& value)
|
|
{
|
|
int temporary;
|
|
ds >> temporary;
|
|
switch (temporary) {
|
|
case Qt::DotLine:
|
|
[[fallthrough]];
|
|
case Qt::DashLine:
|
|
[[fallthrough]];
|
|
case Qt::DashDotLine:
|
|
[[fallthrough]];
|
|
case Qt::DashDotDotLine:
|
|
value = static_cast<Qt::PenStyle>(temporary);
|
|
break;
|
|
case Qt::SolidLine:
|
|
[[fallthrough]];
|
|
default:
|
|
// Force anything else to be a solidline
|
|
value = Qt::SolidLine;
|
|
}
|
|
return ds;
|
|
}
|
|
|
|
static const QColor scDefaultHighlightForeground(QColor(255, 150, 0));
|
|
static const QColor scDefaultHighlightBackground(QColor(0, 0, 0));
|
|
|
|
TRoom::TRoom(TRoomDB* pRDB)
|
|
: highlightColor(scDefaultHighlightForeground)
|
|
, highlightColor2(scDefaultHighlightBackground)
|
|
, mpRoomDB(pRDB)
|
|
{
|
|
}
|
|
|
|
TRoom::~TRoom()
|
|
{
|
|
if (mpRoomDB && !mpRoomDB->mBulkDeletionMode) {
|
|
mpRoomDB->__removeRoom(id);
|
|
}
|
|
}
|
|
|
|
QString TRoom::dirCodeToDisplayName(const int dirCode) const
|
|
{
|
|
switch (dirCode) {
|
|
case DIR_NORTH:
|
|
return tr("North");
|
|
case DIR_NORTHEAST:
|
|
return tr("North-east");
|
|
case DIR_NORTHWEST:
|
|
return tr("North-west");
|
|
case DIR_SOUTH:
|
|
return tr("South");
|
|
case DIR_SOUTHEAST:
|
|
return tr("South-east");
|
|
case DIR_SOUTHWEST:
|
|
return tr("South-west");
|
|
case DIR_EAST:
|
|
return tr("East");
|
|
case DIR_WEST:
|
|
return tr("West");
|
|
case DIR_UP:
|
|
return tr("Up");
|
|
case DIR_DOWN:
|
|
return tr("Down");
|
|
case DIR_IN:
|
|
return tr("In");
|
|
case DIR_OUT:
|
|
return tr("Out");
|
|
case DIR_OTHER:
|
|
return tr("Other");
|
|
default:
|
|
return tr("Unknown");
|
|
}
|
|
}
|
|
|
|
/* static */ QString TRoom::dirCodeToShortString(const int dirCode)
|
|
{
|
|
switch (dirCode) {
|
|
case DIR_NORTH:
|
|
return QLatin1String("n");
|
|
case DIR_NORTHEAST:
|
|
return QLatin1String("ne");
|
|
case DIR_NORTHWEST:
|
|
return QLatin1String("nw");
|
|
case DIR_EAST:
|
|
return QLatin1String("e");
|
|
case DIR_WEST:
|
|
return QLatin1String("w");
|
|
case DIR_SOUTH:
|
|
return QLatin1String("s");
|
|
case DIR_SOUTHEAST:
|
|
return QLatin1String("se");
|
|
case DIR_SOUTHWEST:
|
|
return QLatin1String("sw");
|
|
case DIR_UP:
|
|
return QLatin1String("up");
|
|
case DIR_DOWN:
|
|
return QLatin1String("down");
|
|
case DIR_IN:
|
|
return QLatin1String("in");
|
|
case DIR_OUT:
|
|
return QLatin1String("out");
|
|
default:
|
|
Q_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
/* static */ QString TRoom::dirCodeToString(const int dirCode)
|
|
{
|
|
switch (dirCode) {
|
|
case DIR_NORTH:
|
|
return QLatin1String("north");
|
|
case DIR_NORTHEAST:
|
|
return QLatin1String("northeast");
|
|
case DIR_NORTHWEST:
|
|
return QLatin1String("northwest");
|
|
case DIR_EAST:
|
|
return QLatin1String("east");
|
|
case DIR_WEST:
|
|
return QLatin1String("west");
|
|
case DIR_SOUTH:
|
|
return QLatin1String("south");
|
|
case DIR_SOUTHEAST:
|
|
return QLatin1String("southeast");
|
|
case DIR_SOUTHWEST:
|
|
return QLatin1String("southwest");
|
|
case DIR_UP:
|
|
return QLatin1String("up");
|
|
case DIR_DOWN:
|
|
return QLatin1String("down");
|
|
case DIR_IN:
|
|
return QLatin1String("in");
|
|
case DIR_OUT:
|
|
return QLatin1String("out");
|
|
default:
|
|
Q_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
int TRoom::stringToDirCode(const QString& string) const
|
|
{
|
|
if (string == QLatin1String("north")) {
|
|
return DIR_NORTH;
|
|
}
|
|
if (string == QLatin1String("east")) {
|
|
return DIR_EAST;
|
|
}
|
|
if (string == QLatin1String("south")) {
|
|
return DIR_SOUTH;
|
|
}
|
|
if (string == QLatin1String("west")) {
|
|
return DIR_WEST;
|
|
}
|
|
if (string == QLatin1String("up")) {
|
|
return DIR_UP;
|
|
}
|
|
if (string == QLatin1String("down")) {
|
|
return DIR_DOWN;
|
|
}
|
|
if (string == QLatin1String("northeast")) {
|
|
return DIR_NORTHEAST;
|
|
}
|
|
if (string == QLatin1String("northwest")) {
|
|
return DIR_NORTHWEST;
|
|
}
|
|
if (string == QLatin1String("southeast")) {
|
|
return DIR_SOUTHEAST;
|
|
}
|
|
if (string == QLatin1String("southwest")) {
|
|
return DIR_SOUTHWEST;
|
|
}
|
|
if (string == QLatin1String("in")) {
|
|
return DIR_IN;
|
|
}
|
|
if (string == QLatin1String("out")) {
|
|
return DIR_OUT;
|
|
}
|
|
return DIR_OTHER;
|
|
}
|
|
|
|
bool TRoom::hasExitStub(int direction)
|
|
{
|
|
if (exitStubs.contains(direction)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TRoom::setExitStub(int direction, bool status)
|
|
{
|
|
if (status) {
|
|
if (!hasExit(direction)) {
|
|
if (!exitStubs.contains(direction)) {
|
|
// Previous code did not check for an existing entry for given
|
|
// direction and QList container permits duplicates of same value!
|
|
exitStubs.append(direction);
|
|
}
|
|
} else {
|
|
mpRoomDB->mpMap->logError(tr("Cannot set exit stub in given direction in RoomID %1. There is already an exit there!")
|
|
.arg(QString::number(id)));
|
|
// Since there is no change don't proceed to mark map as needing saving
|
|
return;
|
|
}
|
|
} else {
|
|
if (!exitStubs.removeAll(direction)) {
|
|
// Since there is no change don't proceed to mark map as needing saving
|
|
return;
|
|
}
|
|
}
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
}
|
|
|
|
int TRoom::getExitWeight(const QString& cmd)
|
|
{
|
|
if (exitWeights.contains(cmd)) {
|
|
return exitWeights[cmd];
|
|
}
|
|
return weight; // NOTE: if no exit weight has been set: exit weight = room weight
|
|
}
|
|
|
|
// NOTE: needed so dialogRoomExit code can tell if an exit weight has been set
|
|
// now that they are private!
|
|
bool TRoom::hasExitWeight(const QString& cmd)
|
|
{
|
|
if (exitWeights.contains(cmd)) {
|
|
if (exitWeights.value(cmd) > 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TRoom::setWeight(int w)
|
|
{
|
|
if (w < 1) {
|
|
w = 1;
|
|
}
|
|
weight = w;
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
}
|
|
|
|
void TRoom::setHidden(const bool isHidden)
|
|
{
|
|
if (hidden != isHidden) {
|
|
hidden = isHidden;
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
}
|
|
}
|
|
|
|
void TRoom::setExitWeight(const QString& cmd, int w)
|
|
{
|
|
if (w > 0) {
|
|
exitWeights[cmd] = w;
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
mpRoomDB->mpMap->mMapGraphNeedsUpdate = true;
|
|
} else if (exitWeights.contains(cmd)) {
|
|
exitWeights.remove(cmd);
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
mpRoomDB->mpMap->mMapGraphNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
// Uses lower case initials: n,ne,e,se,s,sw,w,nw
|
|
//
|
|
// also: up, down, in, out or any unprefixed special exit command
|
|
// all of which can be stored but aren't (yet?) showable on the 2D mapper
|
|
bool TRoom::setDoor(const QString& cmd, const int doorStatus)
|
|
{
|
|
if (doorStatus > 0 && doorStatus <= 3) {
|
|
if (doors.value(cmd, 0) != doorStatus) {
|
|
// .value will return 0 if there ISN'T a door for this cmd
|
|
doors[cmd] = doorStatus;
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
return true; // As we have changed things
|
|
}
|
|
return false; // Valid but ineffective
|
|
}
|
|
if (doors.contains(cmd) && !doorStatus) {
|
|
doors.remove(cmd);
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
return true; // As we have changed things
|
|
}
|
|
return false; // As we have not changed things
|
|
}
|
|
|
|
int TRoom::getDoor(const QString& cmd) const
|
|
{
|
|
return doors.value(cmd, 0);
|
|
// Second argument is the result if cmd is not in the doors QMap
|
|
}
|
|
|
|
void TRoom::setId(const int roomId)
|
|
{
|
|
id = roomId;
|
|
}
|
|
|
|
// The second optional argument delays area related recaluclations when true
|
|
// until called with false (the default) - it records the "dirty" areas so that
|
|
// the affected areas can be identified.
|
|
// The caller, should set the argument true for all but the last when working
|
|
// through a list of rooms.
|
|
// There IS a theoretical risk that if the last called room "doesn't exist" then
|
|
// the area related recalculations won't get done - so had better provide an
|
|
// alternative means to do them as a fault recovery
|
|
bool TRoom::setArea(int areaID, bool deferAreaRecalculations)
|
|
{
|
|
// Track dirty areas by ID rather than TArea* across calls: a TArea* held
|
|
// through a deferred window can dangle if the map is cleared in between
|
|
// (e.g. clearMapDB(), profile close, bulk map reload). Looking up the
|
|
// pointer freshly at flush time safely skips areas that no longer exist.
|
|
static QSet<int> dirtyAreaIds;
|
|
TArea* pA = mpRoomDB->getArea(areaID);
|
|
if (!pA) {
|
|
// There is no TArea instance with that _areaID
|
|
// So try and make it
|
|
mpRoomDB->addArea(areaID);
|
|
pA = mpRoomDB->getArea(areaID);
|
|
if (!pA) { // Oh dear, THAT didn't work
|
|
mpRoomDB->mpMap->logError(tr("Requested AreaID %1 did not exist and could not be created. Note: Area numbers must be greater than zero!")
|
|
.arg(QString::number(areaID)));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//remove from the old area
|
|
TArea* pA2 = mpRoomDB->getArea(area);
|
|
if (pA2) {
|
|
pA2->removeRoom(id, deferAreaRecalculations);
|
|
// Ah, all rooms in the OLD area that led to the room now become area
|
|
// exits for that OLD area {so must run determineAreaExits() for the
|
|
// old area after the room has moved to the new area see other
|
|
// "if( pA2 )" below} - other exits that led to the room from other
|
|
// areas are still "out of area exits" UNLESS the room moves to the SAME
|
|
// area that the other exits are in.
|
|
// Add to local store of dirty areas
|
|
dirtyAreaIds.insert(area);
|
|
// Flag the area itself in case something goes
|
|
// wrong on last room in a series
|
|
pA2->mIsDirty = true;
|
|
} else {
|
|
//: Although this is reported as an error it is not a problem
|
|
mpRoomDB->mpMap->logError(tr("When setting the Area for RoomID %1 it did not have a current area, this is unexpected but not a problem!")
|
|
.arg(QString::number(id)));
|
|
}
|
|
|
|
area = areaID;
|
|
pA->addRoom(id);
|
|
|
|
dirtyAreaIds.insert(areaID);
|
|
pA->mIsDirty = true;
|
|
|
|
if (!deferAreaRecalculations) {
|
|
// Swap out the set before iterating so that reentrant calls into
|
|
// setArea() from within clean()/determineAreaExits() (e.g. via Lua
|
|
// event handlers) don't mutate the container we're walking.
|
|
QSet<int> toClean;
|
|
toClean.swap(dirtyAreaIds);
|
|
for (const int aid : toClean) {
|
|
if (TArea* pArea = mpRoomDB->getArea(aid)) {
|
|
pArea->clean();
|
|
}
|
|
}
|
|
}
|
|
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
return true;
|
|
}
|
|
|
|
bool TRoom::setExit(const int to, const int direction)
|
|
{
|
|
// FIXME: This along with TRoom->setExit need to be unified to a controller.
|
|
switch (direction) {
|
|
case DIR_NORTH:
|
|
north = to;
|
|
break;
|
|
case DIR_NORTHEAST:
|
|
northeast = to;
|
|
break;
|
|
case DIR_NORTHWEST:
|
|
northwest = to;
|
|
break;
|
|
case DIR_EAST:
|
|
east = to;
|
|
break;
|
|
case DIR_WEST:
|
|
west = to;
|
|
break;
|
|
case DIR_SOUTH:
|
|
south = to;
|
|
break;
|
|
case DIR_SOUTHEAST:
|
|
southeast = to;
|
|
break;
|
|
case DIR_SOUTHWEST:
|
|
southwest = to;
|
|
break;
|
|
case DIR_UP:
|
|
up = to;
|
|
break;
|
|
case DIR_DOWN:
|
|
down = to;
|
|
break;
|
|
case DIR_IN:
|
|
in = to;
|
|
break;
|
|
case DIR_OUT:
|
|
out = to;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
mpRoomDB->updateEntranceMap(this);
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
return true;
|
|
}
|
|
|
|
bool TRoom::hasExit(const int direction) const
|
|
{
|
|
switch (direction) {
|
|
case DIR_NORTH:
|
|
if (north != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_NORTHEAST:
|
|
if (northeast != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_NORTHWEST:
|
|
if (northwest != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_EAST:
|
|
if (east != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_WEST:
|
|
if (west != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_SOUTH:
|
|
if (south != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_SOUTHEAST:
|
|
if (southeast != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_SOUTHWEST:
|
|
if (southwest != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_UP:
|
|
if (up != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_DOWN:
|
|
if (down != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_IN:
|
|
if (in != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
case DIR_OUT:
|
|
if (out != -1) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Confirms whether the text identifies a valid exit, given the context in which
|
|
// it is called it does not have to worry about the casing of "text" as that has
|
|
// already been handled in the current usages:
|
|
bool TRoom::hasExitOrSpecialExit(const QString& text) const
|
|
{
|
|
// First check the normal ones:
|
|
if (text == QLatin1String("n")) {
|
|
return hasExit(DIR_NORTH);
|
|
} else if (text == QLatin1String("ne")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_NORTHEAST);
|
|
} else if (text == QLatin1String("e")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_EAST);
|
|
} else if (text == QLatin1String("se")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_SOUTHEAST);
|
|
} else if (text == QLatin1String("s")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_SOUTH);
|
|
} else if (text == QLatin1String("sw")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_SOUTHWEST);
|
|
} else if (text == QLatin1String("w")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_WEST);
|
|
} else if (text == QLatin1String("nw")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_NORTHWEST);
|
|
} else if (text == QLatin1String("up")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_UP);
|
|
} else if (text == QLatin1String("down")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_DOWN);
|
|
} else if (text == QLatin1String("in")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_IN);
|
|
} else if (text == QLatin1String("out")) { // NOLINT(readability-else-after-return)
|
|
return hasExit(DIR_OUT);
|
|
} else { // NOLINT(readability-else-after-return)
|
|
// Then check the special exits:
|
|
return mSpecialExits.contains(text);
|
|
}
|
|
}
|
|
|
|
int TRoom::getExit(const int direction) const
|
|
{
|
|
switch (direction) {
|
|
case DIR_NORTH:
|
|
return north;
|
|
case DIR_NORTHEAST:
|
|
return northeast;
|
|
case DIR_NORTHWEST:
|
|
return northwest;
|
|
case DIR_EAST:
|
|
return east;
|
|
case DIR_WEST:
|
|
return west;
|
|
case DIR_SOUTH:
|
|
return south;
|
|
case DIR_SOUTHEAST:
|
|
return southeast;
|
|
case DIR_SOUTHWEST:
|
|
return southwest;
|
|
case DIR_UP:
|
|
return up;
|
|
case DIR_DOWN:
|
|
return down;
|
|
case DIR_IN:
|
|
return in;
|
|
case DIR_OUT:
|
|
return out;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
QHash<int, int> TRoom::getExits() const
|
|
{
|
|
// key is room id we exit to, value is type of exit. 0 is normal, 1 is special
|
|
QHash<int, int> exitList;
|
|
if (north != -1) {
|
|
exitList[north] = 0;
|
|
}
|
|
if (northeast != -1) {
|
|
exitList[northeast] = 0;
|
|
}
|
|
if (east != -1) {
|
|
exitList[east] = 0;
|
|
}
|
|
if (southeast != -1) {
|
|
exitList[southeast] = 0;
|
|
}
|
|
if (south != -1) {
|
|
exitList[south] = 0;
|
|
}
|
|
if (southwest != -1) {
|
|
exitList[southwest] = 0;
|
|
}
|
|
if (west != -1) {
|
|
exitList[west] = 0;
|
|
}
|
|
if (northwest != -1) {
|
|
exitList[northwest] = 0;
|
|
}
|
|
if (up != -1) {
|
|
exitList[up] = 0;
|
|
}
|
|
if (down != -1) {
|
|
exitList[down] = 0;
|
|
}
|
|
if (in != -1) {
|
|
exitList[in] = 0;
|
|
}
|
|
if (out != -1) {
|
|
exitList[out] = 0;
|
|
}
|
|
QMapIterator<QString, int> it(mSpecialExits);
|
|
while (it.hasNext()) {
|
|
it.next();
|
|
exitList[it.value()] = 1;
|
|
}
|
|
return exitList;
|
|
}
|
|
|
|
void TRoom::setExitLock(int exit, bool state)
|
|
{
|
|
if (state) {
|
|
if ((!exitLocks.contains(exit)) && (exit >= DIR_NORTH && exit <= DIR_OUT)) {
|
|
exitLocks.push_back(exit);
|
|
}
|
|
} else {
|
|
exitLocks.removeAll(exit);
|
|
}
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
}
|
|
|
|
bool TRoom::setSpecialExitLock(const QString& cmd, const bool doLock)
|
|
{
|
|
if (!mSpecialExits.contains(cmd)) {
|
|
return false;
|
|
}
|
|
|
|
if (doLock) {
|
|
mSpecialExitLocks.insert(cmd);
|
|
} else {
|
|
mSpecialExitLocks.remove(cmd);
|
|
}
|
|
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
return true;
|
|
}
|
|
|
|
bool TRoom::hasExitLock(const int dir) const
|
|
{
|
|
return exitLocks.contains(dir);
|
|
}
|
|
|
|
bool TRoom::hasSpecialExitLock(const QString& cmd) const
|
|
{
|
|
return mSpecialExitLocks.contains(cmd);
|
|
}
|
|
|
|
// Original addSpecialExit...() code had limitation that it used the "to" room
|
|
// as part of the things to look for to identify a particular special exit
|
|
// indeed the use of the "to" room as the key for the "other" exit map does seem
|
|
// a poorer choice than the "command" which is currently the value item...
|
|
// Changing to setSpecialExit(), "to" values less than 1 remove exit...
|
|
void TRoom::setSpecialExit(const int to, const QString& cmd)
|
|
{
|
|
if (cmd.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
if (to > 0) {
|
|
mSpecialExits[cmd] = to;
|
|
} else {
|
|
// Clean up related data:
|
|
customLinesArrow.remove(cmd);
|
|
customLinesColor.remove(cmd);
|
|
customLinesStyle.remove(cmd);
|
|
customLines.remove(cmd);
|
|
exitWeights.remove(cmd);
|
|
doors.remove(cmd);
|
|
mSpecialExitLocks.remove(cmd);
|
|
mSpecialExits.remove(cmd);
|
|
}
|
|
|
|
TArea* pA = mpRoomDB->getArea(area);
|
|
if (pA) {
|
|
pA->determineAreaExitsOfRoom(id);
|
|
// This updates the (TArea *)->exits map even for exit REMOVALS
|
|
}
|
|
mpRoomDB->updateEntranceMap(this);
|
|
mpRoomDB->mpMap->mMapGraphNeedsUpdate = true;
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
}
|
|
|
|
void TRoom::clearSpecialExits()
|
|
{
|
|
if (mSpecialExits.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QMutableMapIterator<QString, int> itSpecialExit(mSpecialExits);
|
|
while (itSpecialExit.hasNext()) {
|
|
itSpecialExit.next();
|
|
// Clean up related elements first:
|
|
mSpecialExitLocks.remove(itSpecialExit.key());
|
|
doors.remove(itSpecialExit.key());
|
|
customLines.remove(itSpecialExit.key());
|
|
customLinesColor.remove(itSpecialExit.key());
|
|
customLinesStyle.remove(itSpecialExit.key());
|
|
customLinesArrow.remove(itSpecialExit.key());
|
|
// Then remove the exit itself from the QMap:
|
|
itSpecialExit.remove();
|
|
}
|
|
mpRoomDB->updateEntranceMap(this);
|
|
mpRoomDB->mpMap->mMapGraphNeedsUpdate = true;
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
}
|
|
|
|
void TRoom::removeAllSpecialExitsToRoom(const int roomId)
|
|
{
|
|
QMutableMapIterator<QString, int> itSpecialExit(mSpecialExits);
|
|
bool exitFound = false;
|
|
while (itSpecialExit.hasNext()) {
|
|
itSpecialExit.next();
|
|
if (itSpecialExit.value() != roomId) {
|
|
continue;
|
|
}
|
|
|
|
exitFound = true;
|
|
// Clean up related elements first:
|
|
mSpecialExitLocks.remove(itSpecialExit.key());
|
|
doors.remove(itSpecialExit.key());
|
|
customLines.remove(itSpecialExit.key());
|
|
customLinesColor.remove(itSpecialExit.key());
|
|
customLinesStyle.remove(itSpecialExit.key());
|
|
customLinesArrow.remove(itSpecialExit.key());
|
|
// Then remove the exit itself from the QMap:
|
|
itSpecialExit.remove();
|
|
}
|
|
|
|
if (exitFound) {
|
|
TArea* pA = mpRoomDB->getArea(area);
|
|
if (pA) {
|
|
pA->determineAreaExitsOfRoom(id);
|
|
}
|
|
mpRoomDB->updateEntranceMap(this);
|
|
mpRoomDB->mpMap->mMapGraphNeedsUpdate = true;
|
|
mpRoomDB->mpMap->setUnsaved(__func__);
|
|
}
|
|
}
|
|
|
|
void TRoom::calcRoomDimensions()
|
|
{
|
|
min_x = mX;
|
|
max_x = mX;
|
|
min_y = mY;
|
|
max_y = mY;
|
|
|
|
if (customLines.empty()) {
|
|
return;
|
|
}
|
|
|
|
QMapIterator<QString, QList<QPointF>> it(customLines);
|
|
while (it.hasNext()) {
|
|
it.next();
|
|
const QList<QPointF>& pointsInLine = it.value();
|
|
if (pointsInLine.empty()) {
|
|
continue;
|
|
}
|
|
for (auto pointInLine : pointsInLine) {
|
|
const qreal pointX = pointInLine.x();
|
|
const qreal pointY = pointInLine.y();
|
|
if (pointX < min_x) {
|
|
min_x = pointX;
|
|
}
|
|
if (pointX > max_x) {
|
|
max_x = pointX;
|
|
}
|
|
if (pointY < min_y) {
|
|
min_y = pointY;
|
|
}
|
|
if (pointY > max_y) {
|
|
max_y = pointY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TRoom::restore(QDataStream& ifs, int roomID, int version)
|
|
{
|
|
id = roomID;
|
|
ifs >> area;
|
|
// Can be useful when analysing suspect map files!
|
|
// qDebug() << "TRoom::restore(...," << roomID << ",...) has AreaId:" << area;
|
|
ifs >> mX;
|
|
ifs >> mY;
|
|
ifs >> mZ;
|
|
ifs >> north;
|
|
ifs >> northeast;
|
|
ifs >> east;
|
|
ifs >> southeast;
|
|
ifs >> south;
|
|
ifs >> southwest;
|
|
ifs >> west;
|
|
ifs >> northwest;
|
|
ifs >> up;
|
|
ifs >> down;
|
|
ifs >> in;
|
|
ifs >> out;
|
|
ifs >> environment;
|
|
ifs >> weight;
|
|
|
|
// force room weight >= 1 otherwise pathfinding chooses random paths.
|
|
if (weight < 1) {
|
|
weight = 1;
|
|
}
|
|
|
|
if (version < 8) {
|
|
float f1, f2, f3, f4;
|
|
ifs >> f1; //rooms[i]->xRot;
|
|
ifs >> f2; //rooms[i]->yRot;
|
|
ifs >> f3; //rooms[i]->zRot;
|
|
ifs >> f4; //rooms[i]->zoom;
|
|
}
|
|
ifs >> name;
|
|
ifs >> isLocked;
|
|
if (version >= 21) {
|
|
ifs >> hidden;
|
|
ifs >> mSpecialExits;
|
|
} else if (version >= 6) {
|
|
// Before version 21 the special exits were stored as a QMultiMap<int, QString>
|
|
// with the lock information prepended as a '1' locked or '0' unlocked
|
|
// from version 11-ish:
|
|
QMultiMap<int, QString> oldSpecialExits;
|
|
ifs >> oldSpecialExits;
|
|
QMultiMapIterator<int, QString> itOldSpecialExit(oldSpecialExits);
|
|
while (itOldSpecialExit.hasNext()) {
|
|
itOldSpecialExit.next();
|
|
const QString cmd{itOldSpecialExit.value()};
|
|
if (cmd.startsWith(QLatin1String("1"))) {
|
|
// Is locked:
|
|
mSpecialExits.insert(cmd.mid(1), itOldSpecialExit.key());
|
|
mSpecialExitLocks.insert(cmd.mid(1));
|
|
} else if (Q_LIKELY(cmd.startsWith(QLatin1String("0")))) {
|
|
// Is not locked:
|
|
mSpecialExits.insert(cmd.mid(1), itOldSpecialExit.key());
|
|
} else {
|
|
// Has no lock prefix at all
|
|
mSpecialExits.insert(cmd, itOldSpecialExit.key());
|
|
}
|
|
}
|
|
}
|
|
|
|
qint8 oldCharacterCode = 0;
|
|
if (version >= 19) {
|
|
// From version 19 we use a QString for one or more (Unicode) Graphemes
|
|
ifs >> mSymbol;
|
|
} else if (version >= 9 && version < 19) {
|
|
// For older versions we note the prior unsigned short in case
|
|
// there is no fallback carried in the room user data
|
|
ifs >> oldCharacterCode;
|
|
}
|
|
|
|
if (version >= 21) {
|
|
ifs >> mSymbolColor;
|
|
}
|
|
|
|
if (version >= 10) {
|
|
ifs >> userData;
|
|
// Recover and remove fallback values from the user data:
|
|
if (version < 21) {
|
|
const QString hiddenString = userData.take(QLatin1String("system.fallback_hidden"));
|
|
if (!hiddenString.compare(QLatin1String("true"), Qt::CaseInsensitive)) {
|
|
hidden = true;
|
|
}
|
|
} else {
|
|
// The stream carries the authoritative value so any copy of the
|
|
// fallback key in the user data is stale:
|
|
userData.remove(QLatin1String("system.fallback_hidden"));
|
|
}
|
|
if (version >= 19) {
|
|
// Clean up a stale fallback key that past versions could leave
|
|
// behind in the live room's user data (and thus in files saved
|
|
// from it) after saving in a format before 19:
|
|
userData.remove(QLatin1String("system.fallback_symbol"));
|
|
} else {
|
|
const QString symbolString = userData.take(QLatin1String("system.fallback_symbol"));
|
|
if (!symbolString.isEmpty()) {
|
|
// There is a fallback in the user data
|
|
mSymbol = symbolString;
|
|
} else if (oldCharacterCode > 32) {
|
|
// There is an old format unsigned short represeting a printable
|
|
// ASCII or ISO 8859-1 (Latin1) character:
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
|
|
mSymbol = QChar(static_cast<uint>(oldCharacterCode));
|
|
#else
|
|
mSymbol = QChar(oldCharacterCode);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
if (version < 21) {
|
|
auto symbolColorFallbackKey = QLatin1String("system.fallback_symbol_color");
|
|
if (userData.contains(symbolColorFallbackKey)) {
|
|
mSymbolColor = QColor(userData.take(symbolColorFallbackKey));
|
|
}
|
|
} else {
|
|
// The stream carries the authoritative value so any copy of the
|
|
// fallback key in the user data is stale:
|
|
userData.remove(QLatin1String("system.fallback_symbol_color"));
|
|
}
|
|
|
|
// Border properties are stored in userData (not binary stream) to avoid map bloat
|
|
if (userData.contains(ROOM_UI_BORDERCOLOR)) {
|
|
mBorderColor = QColor(userData.value(ROOM_UI_BORDERCOLOR));
|
|
}
|
|
if (userData.contains(ROOM_UI_BORDERTHICKNESS)) {
|
|
int thickness = userData.value(ROOM_UI_BORDERTHICKNESS).toInt();
|
|
if (thickness > 0 && thickness <= 10) {
|
|
mBorderThickness = thickness;
|
|
}
|
|
}
|
|
|
|
if (version >= 11) {
|
|
if (version >= 20) {
|
|
// In version 20 we stopped storing a QString form for the line
|
|
// style - and we also switched to lower case for the normal exit
|
|
// direction keys and using a QColor to store the color:
|
|
ifs >> customLines;
|
|
ifs >> customLinesArrow;
|
|
ifs >> customLinesColor;
|
|
ifs >> customLinesStyle;
|
|
} else {
|
|
QMap<QString, QList<QPointF>> oldLinesData;
|
|
ifs >> oldLinesData;
|
|
QMapIterator<QString, QList<QPointF>> itCustomLine(oldLinesData);
|
|
while (itCustomLine.hasNext()) {
|
|
itCustomLine.next();
|
|
const QString direction(itCustomLine.key());
|
|
if (direction == QLatin1String("N") || direction == QLatin1String("E") || direction == QLatin1String("S") || direction == QLatin1String("W") || direction == QLatin1String("UP")
|
|
|| direction == QLatin1String("DOWN") || direction == QLatin1String("NE") || direction == QLatin1String("SE") || direction == QLatin1String("SW")
|
|
|| direction == QLatin1String("NW") || direction == QLatin1String("IN") || direction == QLatin1String("OUT")) {
|
|
customLines.insert(itCustomLine.key().toLower(), itCustomLine.value());
|
|
} else {
|
|
customLines.insert(itCustomLine.key(), itCustomLine.value());
|
|
}
|
|
}
|
|
|
|
QMap<QString, bool> oldLinesArrowData;
|
|
ifs >> oldLinesArrowData;
|
|
QMapIterator<QString, bool> itCustomLineArrow(oldLinesArrowData);
|
|
while (itCustomLineArrow.hasNext()) {
|
|
itCustomLineArrow.next();
|
|
const QString direction(itCustomLineArrow.key());
|
|
if (direction == QLatin1String("N") || direction == QLatin1String("E") || direction == QLatin1String("S") || direction == QLatin1String("W") || direction == QLatin1String("UP")
|
|
|| direction == QLatin1String("DOWN") || direction == QLatin1String("NE") || direction == QLatin1String("SE") || direction == QLatin1String("SW")
|
|
|| direction == QLatin1String("NW") || direction == QLatin1String("IN") || direction == QLatin1String("OUT")) {
|
|
customLinesArrow.insert(itCustomLineArrow.key().toLower(), itCustomLineArrow.value());
|
|
} else {
|
|
customLinesArrow.insert(itCustomLineArrow.key(), itCustomLineArrow.value());
|
|
}
|
|
}
|
|
|
|
QMap<QString, QList<int>> oldLinesColorData;
|
|
ifs >> oldLinesColorData;
|
|
QMapIterator<QString, QList<int>> itCustomLineColor(oldLinesColorData);
|
|
while (itCustomLineColor.hasNext()) {
|
|
itCustomLineColor.next();
|
|
const QString direction(itCustomLineColor.key());
|
|
if (direction == QLatin1String("N") || direction == QLatin1String("E") || direction == QLatin1String("S") || direction == QLatin1String("W") || direction == QLatin1String("UP")
|
|
|| direction == QLatin1String("DOWN") || direction == QLatin1String("NE") || direction == QLatin1String("SE") || direction == QLatin1String("SW")
|
|
|| direction == QLatin1String("NW") || direction == QLatin1String("IN") || direction == QLatin1String("OUT")) {
|
|
// Fixup broken custom lines caused by maps saved prior to
|
|
// https://github.com/Mudlet/Mudlet/pull/2559 going into the
|
|
// code by only adding the value if it contains enough
|
|
// colour components to be a valid colour:
|
|
if (itCustomLineColor.value().count() > 2) {
|
|
customLinesColor.insert(itCustomLineColor.key().toLower(), QColor(itCustomLineColor.value().at(0), itCustomLineColor.value().at(1), itCustomLineColor.value().at(2)));
|
|
}
|
|
// Otherwise we will fixup both empty
|
|
// itCustomLineColor.value() entities AND altogether missing
|
|
// ones outside of the while() {...}:
|
|
} else {
|
|
if (itCustomLineColor.value().count() > 2) {
|
|
customLinesColor.insert(itCustomLineColor.key(), QColor(itCustomLineColor.value().at(0), itCustomLineColor.value().at(1), itCustomLineColor.value().at(2)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create new (RED) colour customLinesColor entities for any custom
|
|
// exit lines that does not have one or has an empty one as a result
|
|
// of https://github.com/Mudlet/Mudlet/issues/2558 - use a QSet
|
|
// rather than a QList when manipulating the present/absent
|
|
// direction keys as there won't be any duplicate keys and the
|
|
// operation we want to perform (obtain the directions of all custom
|
|
// exit lines and remove those which are already included in the
|
|
// colours) is much easier to perform on a QSet rather than a QList:
|
|
auto customLineKeys = customLines.keys();
|
|
QSet<QString> missingKeys{customLineKeys.begin(), customLineKeys.end()};
|
|
if (!customLinesColor.isEmpty()) {
|
|
auto customLinesColorKeys = customLinesColor.keys();
|
|
QSet<QString> const customLinesColorKeysSet{customLinesColorKeys.begin(), customLinesColorKeys.end()};
|
|
missingKeys.subtract(customLinesColorKeysSet);
|
|
}
|
|
QSetIterator<QString> itMissingCustomLineColourKey(missingKeys);
|
|
while (itMissingCustomLineColourKey.hasNext()) {
|
|
customLinesColor.insert(itMissingCustomLineColourKey.next(), QColor(Qt::red));
|
|
}
|
|
|
|
QMap<QString, QString> oldLineStyleData;
|
|
ifs >> oldLineStyleData;
|
|
QMapIterator<QString, QString> itCustomLineStyle(oldLineStyleData);
|
|
while (itCustomLineStyle.hasNext()) {
|
|
itCustomLineStyle.next();
|
|
QString direction(itCustomLineStyle.key());
|
|
if (direction == QLatin1String("N") || direction == QLatin1String("E") || direction == QLatin1String("S") || direction == QLatin1String("W") || direction == QLatin1String("UP")
|
|
|| direction == QLatin1String("DOWN") || direction == QLatin1String("NE") || direction == QLatin1String("SE") || direction == QLatin1String("SW")
|
|
|| direction == QLatin1String("NW") || direction == QLatin1String("IN") || direction == QLatin1String("OUT")) {
|
|
direction = direction.toLower();
|
|
}
|
|
|
|
if (itCustomLineStyle.value() == QLatin1String("dot line")) {
|
|
customLinesStyle.insert(direction, Qt::DotLine);
|
|
} else if (itCustomLineStyle.value() == QLatin1String("dash line")) {
|
|
customLinesStyle.insert(direction, Qt::DashLine);
|
|
} else if (itCustomLineStyle.value() == QLatin1String("dash dot line")) {
|
|
customLinesStyle.insert(direction, Qt::DashDotLine);
|
|
} else if (itCustomLineStyle.value() == QLatin1String("dash dot dot line")) {
|
|
customLinesStyle.insert(direction, Qt::DashDotDotLine);
|
|
} else {
|
|
customLinesStyle.insert(direction, Qt::SolidLine);
|
|
}
|
|
}
|
|
}
|
|
if (version >= 21) {
|
|
ifs >> mSpecialExitLocks;
|
|
}
|
|
ifs >> exitLocks;
|
|
}
|
|
if (version >= 13) {
|
|
ifs >> exitStubs;
|
|
}
|
|
if (version >= 16) {
|
|
ifs >> exitWeights;
|
|
ifs >> doors;
|
|
}
|
|
calcRoomDimensions();
|
|
}
|
|
|
|
void TRoom::audit(const QHash<int, int> roomRemapping, const QHash<int, int> areaRemapping)
|
|
{
|
|
if (areaRemapping.contains(area)) {
|
|
userData.insert(qsl("audit.remapped_area"), QString::number(area));
|
|
area = areaRemapping.value(area);
|
|
}
|
|
|
|
auditExits(roomRemapping);
|
|
}
|
|
|
|
void TRoom::auditExits(const QHash<int, int> roomRemapping)
|
|
{
|
|
// Clone all the structures into working copies that we can eliminate valid
|
|
// members from to identify any rogue members before removing them:
|
|
|
|
QMap<QString, int> exitWeightsCopy = exitWeights;
|
|
QSet<int> exitStubsCopy{exitStubs.begin(), exitStubs.end()};
|
|
QSet<int> exitLocksCopy{exitLocks.begin(), exitLocks.end()};
|
|
QMap<QString, int> doorsCopy = doors;
|
|
QMap<QString, QList<QPointF>> customLinesCopy = customLines;
|
|
QMap<QString, QColor> customLinesColorCopy = customLinesColor;
|
|
QMap<QString, Qt::PenStyle> customLinesStyleCopy = customLinesStyle;
|
|
QMap<QString, bool> customLinesArrowCopy = customLinesArrow;
|
|
|
|
exitWeightsCopy.detach(); // Make deep copies now, this will happen anyhow once we start to remove valid members
|
|
exitStubsCopy.detach();
|
|
exitLocksCopy.detach();
|
|
doorsCopy.detach();
|
|
customLinesCopy.detach();
|
|
customLinesColorCopy.detach();
|
|
customLinesStyleCopy.detach();
|
|
customLinesArrowCopy.detach();
|
|
|
|
auditExit(north,
|
|
DIR_NORTH,
|
|
tr("North"),
|
|
QLatin1String("n"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(northeast,
|
|
DIR_NORTHEAST,
|
|
tr("Northeast"),
|
|
QLatin1String("ne"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(northwest,
|
|
DIR_NORTHWEST,
|
|
tr("Northwest"),
|
|
QLatin1String("nw"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(south,
|
|
DIR_SOUTH,
|
|
tr("South"),
|
|
QLatin1String("s"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(southeast,
|
|
DIR_SOUTHEAST,
|
|
tr("Southeast"),
|
|
QLatin1String("se"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(southwest,
|
|
DIR_SOUTHWEST,
|
|
tr("Southwest"),
|
|
QLatin1String("sw"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(east,
|
|
DIR_EAST,
|
|
tr("East"),
|
|
QLatin1String("e"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(west,
|
|
DIR_WEST,
|
|
tr("West"),
|
|
QLatin1String("w"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(up,
|
|
DIR_UP,
|
|
tr("Up"),
|
|
QLatin1String("up"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(down,
|
|
DIR_DOWN,
|
|
tr("Down"),
|
|
QLatin1String("down"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(in,
|
|
DIR_IN,
|
|
tr("In"),
|
|
QLatin1String("in"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
auditExit(out,
|
|
DIR_OUT,
|
|
tr("Out"),
|
|
QLatin1String("out"),
|
|
exitWeightsCopy,
|
|
exitStubsCopy,
|
|
exitLocksCopy,
|
|
doorsCopy,
|
|
customLinesCopy,
|
|
customLinesColorCopy,
|
|
customLinesStyleCopy,
|
|
customLinesArrowCopy,
|
|
roomRemapping);
|
|
|
|
// If we use the Mutable iterator we don't have to restart after a deletion
|
|
{ // Block code to limit scope of iterator
|
|
QMutableMapIterator<QString, int> it(mSpecialExits);
|
|
while (it.hasNext()) {
|
|
it.next();
|
|
const QString exitName = it.key();
|
|
const int exitRoomId = it.value();
|
|
if (exitName.isEmpty()) {
|
|
//: %1 is the room ID, %2 is the destination room ID
|
|
const QString warnMsg =
|
|
tr("[ WARN ] - In room ID: %1 removing invalid (special) exit to %2 (with no name!)").arg(id, 6, 10, QLatin1Char('0')).arg(exitRoomId, 6, 10, QLatin1Char('0'));
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(warnMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, warnMsg);
|
|
it.remove();
|
|
continue;
|
|
}
|
|
|
|
if (roomRemapping.contains(exitRoomId)) {
|
|
const QString auditKey = qsl("audit.remapped_special_exit.%1").arg(exitName);
|
|
userData.insert(auditKey, QString::number(exitRoomId));
|
|
//: %1 is the room ID, %2 is the exit name, %3 is the old destination room ID, %4 is the new destination room ID
|
|
const QString infoMsg = tr(R"([ INFO ] - In room with ID: %1 correcting special exit "%2" that was to room with an exit to invalid room: %3 to now go to: %4.)")
|
|
.arg(id)
|
|
.arg(exitName)
|
|
.arg(exitRoomId)
|
|
.arg(roomRemapping.value(exitRoomId));
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(infoMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, infoMsg);
|
|
it.setValue(roomRemapping.value(exitRoomId));
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
// Now check for the validity of the special exit room destinations after
|
|
// remapping - and clean up any exit elements related to the invalid or
|
|
// missing ones
|
|
QMutableMapIterator<QString, int> it(mSpecialExits);
|
|
while (it.hasNext()) {
|
|
it.next();
|
|
const int exitRoomId = it.value();
|
|
const QString exitName = it.key();
|
|
|
|
if (exitRoomId > 0) {
|
|
// A real exit - should have a real destination
|
|
if (Q_UNLIKELY(!mpRoomDB->getRoom(exitRoomId))) {
|
|
// But it doesn't exist
|
|
const QString auditKey = qsl("audit.removed_valid_but_missing_special_exit.%1").arg(exitName);
|
|
//: %1 is the room ID, %2 is the exit name, %3 is the destination room ID, %4 is the audit key
|
|
const QString warnMsg =
|
|
tr(R"([ WARN ] - Room with ID: %1 has a special exit "%2" with an exit to: %3 but that room does not exist. The exit will be removed (but the destination room ID will be stored in the room user data under a key: "%4").)")
|
|
.arg(id)
|
|
.arg(exitName)
|
|
.arg(exitRoomId)
|
|
.arg(auditKey);
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(warnMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, warnMsg, true);
|
|
userData.insert(auditKey, QString::number(exitRoomId));
|
|
it.remove();
|
|
|
|
// Remove the corresponding things from the pools of things
|
|
// that have to be checked:
|
|
// TODO: Add additional warnings if we ARE deleting any data in following
|
|
exitWeights.remove(exitName);
|
|
doors.remove(exitName);
|
|
customLines.remove(exitName);
|
|
customLinesColor.remove(exitName);
|
|
customLinesStyle.remove(exitName);
|
|
customLinesArrow.remove(exitName);
|
|
exitWeightsCopy.remove(exitName);
|
|
doorsCopy.remove(exitName);
|
|
customLinesCopy.remove(exitName);
|
|
customLinesColorCopy.remove(exitName);
|
|
customLinesStyleCopy.remove(exitName);
|
|
customLinesArrowCopy.remove(exitName);
|
|
} else {
|
|
// Exit id is for a room that DOES exist and is in the valid range
|
|
// So remove from the pools of things to check all the things that
|
|
// CAN be associated with this exit direction:
|
|
exitWeightsCopy.remove(exitName);
|
|
doorsCopy.remove(exitName);
|
|
customLinesCopy.remove(exitName);
|
|
customLinesColorCopy.remove(exitName);
|
|
customLinesStyleCopy.remove(exitName);
|
|
customLinesArrowCopy.remove(exitName);
|
|
}
|
|
} else {
|
|
// < 1 and not renumbered because the bad room Id DID NOT exist
|
|
const QString auditKey = qsl("audit.removed_invalid_special_exit.%1").arg(exitName);
|
|
userData.insert(auditKey, QString::number(exitRoomId));
|
|
//: %1 is the room ID, %2 is the exit name, %3 is the invalid destination room ID, %4 is the audit key
|
|
const QString infoMsg =
|
|
tr(R"([ INFO ] - In room with ID: %1 special exit "%2" that was to room with an invalid ID: %3 that does not exist. The exit will be removed (the bad destination room ID will be stored in the room user data under a key: "%4").)")
|
|
.arg(id)
|
|
.arg(exitName)
|
|
.arg(exitRoomId)
|
|
.arg(auditKey);
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(infoMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, infoMsg, true);
|
|
it.remove();
|
|
// We cannot have a door or anything else on a non-existent special exit
|
|
doors.remove(exitName);
|
|
exitWeights.remove(exitName);
|
|
customLines.remove(exitName);
|
|
customLinesColor.remove(exitName);
|
|
customLinesStyle.remove(exitName);
|
|
customLinesArrow.remove(exitName);
|
|
doorsCopy.remove(exitName);
|
|
exitWeightsCopy.remove(exitName);
|
|
customLinesCopy.remove(exitName);
|
|
customLinesColorCopy.remove(exitName);
|
|
customLinesStyleCopy.remove(exitName);
|
|
customLinesArrowCopy.remove(exitName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally check for any left over exit elements that should not be there:
|
|
// Doors:
|
|
if (!doorsCopy.isEmpty()) {
|
|
QStringList extras;
|
|
QMapIterator<QString, int> itSpareDoors(doorsCopy);
|
|
while (itSpareDoors.hasNext()) {
|
|
itSpareDoors.next();
|
|
doors.remove(itSpareDoors.key());
|
|
switch (itSpareDoors.value()) {
|
|
case 0:
|
|
extras.append(tr("%1 {none}").arg(itSpareDoors.key()));
|
|
break;
|
|
case 1:
|
|
extras.append(tr("%1 (open)").arg(itSpareDoors.key()));
|
|
break;
|
|
case 2:
|
|
extras.append(tr("%1 (closed)").arg(itSpareDoors.key()));
|
|
break;
|
|
case 3:
|
|
extras.append(tr("%1 (locked)").arg(itSpareDoors.key()));
|
|
break;
|
|
default:
|
|
extras.append(tr("%1 {invalid}").arg(itSpareDoors.key()));
|
|
}
|
|
}
|
|
//: %1 is the room ID, %2 is a list of door items
|
|
const QString infoMsg = tr("[ INFO ] - In room with ID: %1 found one or more surplus door items that were removed: %2.").arg(id).arg(extras.join(QLatin1String(", ")));
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(infoMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, infoMsg, true);
|
|
}
|
|
|
|
// ExitWeights:
|
|
if (!exitWeightsCopy.isEmpty()) {
|
|
QStringList extras;
|
|
QMapIterator<QString, int> itSpareExitWeight(exitWeightsCopy);
|
|
while (itSpareExitWeight.hasNext()) {
|
|
itSpareExitWeight.next();
|
|
exitWeights.remove(itSpareExitWeight.key());
|
|
extras << qsl("\"%1\"(%2)").arg(itSpareExitWeight.key()).arg(itSpareExitWeight.value());
|
|
}
|
|
//: %1 is the room ID, %2 is a list of weight items
|
|
const QString infoMsg = tr("[ INFO ] - In room with ID: %1 found one or more surplus weight items that were removed: %2.").arg(id).arg(extras.join(QLatin1String(", ")));
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(infoMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, infoMsg, true);
|
|
}
|
|
|
|
// ExitLocks:
|
|
if (!exitLocksCopy.isEmpty()) {
|
|
QStringList extras;
|
|
QSetIterator<int> itSpareExitLock(exitLocksCopy);
|
|
while (itSpareExitLock.hasNext()) {
|
|
const int dirCode = itSpareExitLock.next();
|
|
extras.append(dirCodeToDisplayName(dirCode));
|
|
exitLocks.removeAll(dirCode);
|
|
}
|
|
//: %1 is the room ID, %2 is a list of exit lock items
|
|
const QString infoMsg = tr("[ INFO ] - In room with ID: %1 found one or more surplus exit lock items that were removed: %2.").arg(id).arg(extras.join(QLatin1String(", ")));
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(infoMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, infoMsg, true);
|
|
}
|
|
|
|
// Custom Lines - points - the master element - if the entry for an exit is
|
|
// not valid then remove corresponding entries in other elements
|
|
{
|
|
QStringList extras;
|
|
if (!customLinesCopy.isEmpty()) {
|
|
QMapIterator<QString, QList<QPointF>> itSpareCustomLine(customLinesCopy);
|
|
while (itSpareCustomLine.hasNext()) {
|
|
itSpareCustomLine.next();
|
|
customLines.remove(itSpareCustomLine.key());
|
|
customLinesColor.remove(itSpareCustomLine.key());
|
|
customLinesStyle.remove(itSpareCustomLine.key());
|
|
customLinesArrow.remove(itSpareCustomLine.key());
|
|
customLinesColorCopy.remove(itSpareCustomLine.key());
|
|
customLinesStyleCopy.remove(itSpareCustomLine.key());
|
|
customLinesArrowCopy.remove(itSpareCustomLine.key());
|
|
if (itSpareCustomLine.key().isEmpty()) {
|
|
extras.append("<empty string>");
|
|
} else {
|
|
extras.append(itSpareCustomLine.key());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Custom Lines - colors
|
|
if (!customLinesColorCopy.isEmpty()) {
|
|
QMapIterator<QString, QColor> itSpareCustomLine(customLinesColorCopy);
|
|
while (itSpareCustomLine.hasNext()) {
|
|
itSpareCustomLine.next();
|
|
customLinesColor.remove(itSpareCustomLine.key());
|
|
customLinesStyle.remove(itSpareCustomLine.key());
|
|
customLinesArrow.remove(itSpareCustomLine.key());
|
|
customLinesStyleCopy.remove(itSpareCustomLine.key());
|
|
customLinesArrowCopy.remove(itSpareCustomLine.key());
|
|
if (itSpareCustomLine.key().isEmpty()) {
|
|
extras.append("<empty string>");
|
|
} else {
|
|
extras.append(itSpareCustomLine.key());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Custom Lines - styles
|
|
if (!customLinesStyleCopy.isEmpty()) {
|
|
QMapIterator<QString, Qt::PenStyle> itSpareCustomLine(customLinesStyleCopy);
|
|
while (itSpareCustomLine.hasNext()) {
|
|
itSpareCustomLine.next();
|
|
customLinesStyle.remove(itSpareCustomLine.key());
|
|
customLinesArrow.remove(itSpareCustomLine.key());
|
|
customLinesArrowCopy.remove(itSpareCustomLine.key());
|
|
if (itSpareCustomLine.key().isEmpty()) {
|
|
extras.append("<empty string>");
|
|
} else {
|
|
extras.append(itSpareCustomLine.key());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Custom Lines - ending arrow
|
|
if (!customLinesArrowCopy.isEmpty()) {
|
|
QMapIterator<QString, bool> itSpareCustomLine(customLinesArrowCopy);
|
|
while (itSpareCustomLine.hasNext()) {
|
|
itSpareCustomLine.next();
|
|
customLinesArrow.remove(itSpareCustomLine.key());
|
|
if (itSpareCustomLine.key().isEmpty()) {
|
|
extras.append("<empty string>");
|
|
} else {
|
|
extras.append(itSpareCustomLine.key());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!extras.isEmpty()) {
|
|
//: %1 is the room ID, %2 is a list of custom line elements
|
|
const QString infoMsg = tr("[ INFO ] - In room with ID: %1 found one or more surplus custom line elements that were removed: %2.").arg(id).arg(extras.join(QLatin1String(", ")));
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(infoMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, infoMsg, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TRoom::auditExit(int& exitRoomId, // Reference to where exit goes to
|
|
const int dirCode, // DIR_xxx code for this exit - to access stubs & locks
|
|
const QString displayName, // What to present as the name of the exit
|
|
const QString exitKey, // To access doors, weights and custom exit line elements
|
|
QMap<QString, int>& exitWeightsPool, // References to working copies of things - valid ones will be removed
|
|
QSet<int>& exitStubsPool,
|
|
QSet<int>& exitLocksPool,
|
|
QMap<QString, int>& doorsPool,
|
|
QMap<QString, QList<QPointF>>& customLinesPool,
|
|
QMap<QString, QColor>& customLinesColorPool,
|
|
QMap<QString, Qt::PenStyle>& customLinesStylePool,
|
|
QMap<QString, bool>& customLinesArrowPool,
|
|
const QHash<int, int> roomRemapping)
|
|
{
|
|
if (roomRemapping.contains(exitRoomId)) {
|
|
const QString auditKey = qsl("audit.remapped_exit.%1").arg(dirCode);
|
|
userData.insert(auditKey, QString::number(exitRoomId));
|
|
//: %1 is the room ID, %2 is the exit direction, %3 is the old destination room ID, %4 is the new destination room ID
|
|
const QString infoMsg = tr(R"([ INFO ] - In room with ID: %1 correcting exit "%2" that was to room with an exit to invalid room: %3 to now go to: %4.)")
|
|
.arg(id)
|
|
.arg(displayName)
|
|
.arg(exitRoomId)
|
|
.arg(roomRemapping.value(exitRoomId));
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(infoMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, infoMsg, true);
|
|
exitRoomId = roomRemapping.value(exitRoomId);
|
|
}
|
|
|
|
if (exitRoomId > 0) {
|
|
// A real exit - should have a real destination, and NOT have a stub
|
|
if (Q_UNLIKELY(!mpRoomDB->getRoom(exitRoomId))) {
|
|
// But it doesn't exist
|
|
const QString auditKey = qsl("audit.made_stub_of_valid_but_missing_exit.%1").arg(dirCode);
|
|
//: %1 is the room ID, %2 is the exit direction, %3 is the destination room ID that doesn't exist, %4 is the audit key
|
|
const QString warnMsg =
|
|
tr(R"([ WARN ] - Room with ID: %1 has an exit "%2" to: %3 but that room does not exist. The exit will be removed (but the destination room ID will be stored in the room user data under a key: "%4") and the exit will be turned into a stub.)")
|
|
.arg(id)
|
|
.arg(displayName)
|
|
.arg(exitRoomId)
|
|
.arg(auditKey);
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(warnMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, warnMsg, true);
|
|
userData.insert(auditKey, QString::number(exitRoomId));
|
|
if (!exitStubs.contains(dirCode)) {
|
|
// Add a stub (this is so we can retain doors and locks, though exit weights and custom lines will go)
|
|
exitStubs.append(dirCode);
|
|
// Remove a (now valid) stub in this direction from check pool
|
|
exitStubsPool.remove(dirCode);
|
|
}
|
|
|
|
exitRoomId = -1;
|
|
|
|
// Remove the following things that a stub exit does not handle
|
|
// And eliminate the corresponding things from the pools of things
|
|
// that have to be checked:
|
|
// TODO: Add additional warnings if we ARE deleting any data in following
|
|
exitLocksPool.remove(dirCode); // A stub exit can retain a lock so keep any that exists
|
|
|
|
exitWeights.remove(exitKey);
|
|
exitWeightsPool.remove(exitKey);
|
|
|
|
doorsPool.remove(exitKey); // we now have a stub exit and that can take a door so clear the door from the check pool
|
|
|
|
customLines.remove(exitKey);
|
|
customLinesPool.remove(exitKey);
|
|
|
|
customLinesColor.remove(exitKey);
|
|
customLinesColorPool.remove(exitKey);
|
|
|
|
customLinesStyle.remove(exitKey);
|
|
customLinesStylePool.remove(exitKey);
|
|
|
|
customLinesArrow.remove(exitKey);
|
|
customLinesArrowPool.remove(exitKey);
|
|
} else {
|
|
// we do have a valid exit destination room
|
|
|
|
// We cannot allow a stub exit at the same time as a real exit:
|
|
if (exitStubs.contains(dirCode)) {
|
|
//: %1 is the room ID, %2 is the exit direction, %3 is the destination room ID
|
|
const QString warnMsg =
|
|
tr(R"([ ALERT ] - Room with ID: %1 has an exit "%2" to: %3 but also has a stub exit in the same direction! As a real exit precludes a stub, the latter will be removed.)")
|
|
.arg(id)
|
|
.arg(displayName)
|
|
.arg(exitRoomId);
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(warnMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, warnMsg, true);
|
|
exitStubs.removeAll(dirCode);
|
|
exitStubsPool.remove(dirCode); // Remove the stub in this direction from check pool as we have handled it
|
|
}
|
|
|
|
// Exit id is for a room that DOES exist and is in the valid range
|
|
// So remove from the pools of things to check all the things CAN be
|
|
// associated with this exit direction:
|
|
exitLocksPool.remove(dirCode);
|
|
exitWeightsPool.remove(exitKey);
|
|
doorsPool.remove(exitKey);
|
|
customLinesPool.remove(exitKey);
|
|
customLinesColorPool.remove(exitKey);
|
|
customLinesStylePool.remove(exitKey);
|
|
customLinesArrowPool.remove(exitKey);
|
|
}
|
|
} else if (exitRoomId == -1) {
|
|
// No exit - so do we have a stub?
|
|
if (exitStubs.contains(dirCode)) {
|
|
exitStubsPool.remove(dirCode); // Remove the stub in this direction from check pool as we have handled it
|
|
} else {
|
|
// If NOT we cannot have a door or a lock
|
|
doors.remove(exitKey);
|
|
exitLocks.removeAll(dirCode);
|
|
}
|
|
doorsPool.remove(exitKey);
|
|
exitLocksPool.remove(dirCode);
|
|
|
|
// Whether we do or not have a stub exit we cannot have a custom
|
|
// line or a weight - so remove them if they exist:
|
|
exitWeights.remove(exitKey);
|
|
customLines.remove(exitKey);
|
|
customLinesColor.remove(exitKey);
|
|
customLinesStyle.remove(exitKey);
|
|
customLinesArrow.remove(exitKey);
|
|
// Whether we have a stub or not we have handled all the things that we
|
|
// want to check the existence of so take them out of the pools of
|
|
// things left to check after all the exits have been looked at
|
|
exitWeightsPool.remove(exitKey);
|
|
customLinesPool.remove(exitKey);
|
|
customLinesColorPool.remove(exitKey);
|
|
customLinesStylePool.remove(exitKey);
|
|
customLinesArrowPool.remove(exitKey);
|
|
} else {
|
|
// either 0 or < -1 and not renumbered because the bad room Id DID NOT
|
|
// exist, there could be a "double fault" in that there is also a stub
|
|
// exit, but that will be masked as we turn the exit into a stub anyhow.
|
|
const QString auditKey = qsl("audit.made_stub_of_invalid_exit.%1").arg(dirCode);
|
|
userData.insert(auditKey, QString::number(exitRoomId));
|
|
//: %1 is the room ID, %2 is the exit direction, %3 is the invalid destination room ID, %4 is the audit key
|
|
QString infoMsg =
|
|
tr(R"([ INFO ] - In room with ID: %1 exit "%2" that was to room with an invalid ID: %3 that does not exist. The exit will be removed (the bad destination room ID will be stored in the room user data under a key: "%4") and the exit will be turned into a stub.)")
|
|
.arg(id)
|
|
.arg(displayName)
|
|
.arg(exitRoomId)
|
|
.arg(auditKey);
|
|
exitRoomId = -1;
|
|
|
|
if (!exitStubs.contains(dirCode)) {
|
|
// Add the stub
|
|
exitStubs.append(dirCode);
|
|
}
|
|
exitStubsPool.remove(dirCode); // Remove the stub in this direction from check pool as we have handled it
|
|
|
|
// Any lock on this exit is retained as the stub that replaces it can
|
|
// also carry a lock
|
|
|
|
if (exitWeights.contains(exitKey)) {
|
|
const QString auditKeyWeight = qsl("audit.invalid_exit.%1.weight").arg(dirCode);
|
|
userData.insert(auditKeyWeight, QString::number(exitWeights.value(exitKey)));
|
|
//: %1 is the audit key for the weight
|
|
infoMsg.append(qsl(" %1").arg(tr(R"(It had a weight, this is recorded as user data with key: "%1".)").arg(auditKeyWeight)));
|
|
exitWeights.remove(exitKey);
|
|
}
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(infoMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, infoMsg, true);
|
|
|
|
|
|
if (customLines.contains(exitKey)) {
|
|
const QString warnMsg = tr("[ WARN ] - There was a custom exit line associated with the invalid exit but it has not been possible to salvage this, it has been lost!");
|
|
if (mudlet::self()->showMapAuditErrors()) {
|
|
mpRoomDB->mpMap->postMessage(warnMsg);
|
|
}
|
|
mpRoomDB->mpMap->appendRoomErrorMsg(id, warnMsg, true);
|
|
customLines.remove(exitKey);
|
|
}
|
|
customLinesColor.remove(exitKey);
|
|
customLinesStyle.remove(exitKey);
|
|
customLinesArrow.remove(exitKey);
|
|
// Whether we have a stub or not we have handled all the things that we
|
|
// want to check the existence of so take them out of the pools of
|
|
// things left to check after all the exits have been looked at
|
|
|
|
doorsPool.remove(exitKey); // Can still have a door on a stub
|
|
exitLocksPool.remove(dirCode);
|
|
exitWeightsPool.remove(exitKey);
|
|
customLinesPool.remove(exitKey);
|
|
customLinesColorPool.remove(exitKey);
|
|
customLinesStylePool.remove(exitKey);
|
|
customLinesArrowPool.remove(exitKey);
|
|
}
|
|
}
|
|
|
|
void TRoom::writeJsonRoom(QJsonArray& obj) const
|
|
{
|
|
QJsonObject roomObj;
|
|
// Static casts are used as reminders of what value types are actually used:
|
|
roomObj.insert(QLatin1String("id"), static_cast<double>(id));
|
|
|
|
if (!name.isEmpty()) {
|
|
const QJsonValue nameValue{name};
|
|
roomObj.insert(QLatin1String("name"), nameValue);
|
|
}
|
|
|
|
QJsonArray coordinateArray;
|
|
coordinateArray.append(static_cast<double>(mX));
|
|
coordinateArray.append(static_cast<double>(mY));
|
|
coordinateArray.append(static_cast<double>(mZ));
|
|
const QJsonValue coordinatesValue{coordinateArray};
|
|
roomObj.insert(QLatin1String("coordinates"), coordinatesValue);
|
|
|
|
if (isLocked) {
|
|
roomObj.insert(QLatin1String("locked"), true);
|
|
}
|
|
|
|
if (hidden) {
|
|
roomObj.insert(QLatin1String("hidden"), true);
|
|
}
|
|
|
|
if (weight != 1) {
|
|
roomObj.insert(QLatin1String("weight"), static_cast<double>(weight));
|
|
}
|
|
|
|
if (!mSymbol.isEmpty()) {
|
|
writeJsonSymbol(roomObj);
|
|
}
|
|
|
|
if (mBorderColor.isValid() || mBorderThickness > 0) {
|
|
writeJsonBorder(roomObj);
|
|
}
|
|
|
|
roomObj.insert(QLatin1String("environment"), static_cast<double>(environment));
|
|
|
|
const QString hashForRoomID{mpRoomDB->roomIDToHash.value(id)};
|
|
if (!hashForRoomID.isEmpty()) {
|
|
const QJsonValue hashForRoomIDValue{hashForRoomID};
|
|
roomObj.insert(QLatin1String("hash"), hashForRoomIDValue);
|
|
}
|
|
|
|
// Not currently done in the binary map format so don't save it here either;
|
|
// reader code left active in case that situation changes:
|
|
// writeJsonHighlight(roomObj);
|
|
|
|
writeJsonExits(roomObj);
|
|
|
|
writeJsonExitStubs(roomObj);
|
|
|
|
writeJsonUserData(roomObj);
|
|
|
|
const QJsonValue roomValue{roomObj};
|
|
obj.append(roomValue);
|
|
}
|
|
|
|
int TRoom::readJsonRoom(const QJsonArray& array, const int index, const int areaId)
|
|
{
|
|
const QJsonObject roomObj{array.at(index).toObject()};
|
|
// This is not needed to be stored into id as that is done when the room is
|
|
// added to the TRoomDB via a TRoomDB::addRoom(...) call:
|
|
const int roomId = roomObj.value(QLatin1String("id")).toInt();
|
|
name = roomObj.value(QLatin1String("name")).toString();
|
|
area = areaId;
|
|
readJsonUserData(roomObj.value(QLatin1String("userData")).toObject());
|
|
|
|
const QJsonArray coordinatesArray = roomObj.value(QLatin1String("coordinates")).toArray();
|
|
mX = coordinatesArray.at(0).toInt();
|
|
mY = coordinatesArray.at(1).toInt();
|
|
mZ = coordinatesArray.at(2).toInt();
|
|
|
|
if (roomObj.contains(QLatin1String("locked")) && roomObj.value(QLatin1String("locked")).toBool()) {
|
|
isLocked = true;
|
|
}
|
|
|
|
if (roomObj.contains(QLatin1String("hidden")) && roomObj.value(QLatin1String("hidden")).toBool()) {
|
|
hidden = true;
|
|
}
|
|
|
|
if (roomObj.contains(QLatin1String("weight")) && roomObj.value(QLatin1String("weight")).isDouble()) {
|
|
weight = roomObj.value(QLatin1String("weight")).toInt();
|
|
}
|
|
|
|
if (roomObj.contains(QLatin1String("symbol")) && roomObj.value(QLatin1String("symbol")).isObject()) {
|
|
readJsonSymbol(roomObj);
|
|
}
|
|
|
|
if (roomObj.contains(QLatin1String("border")) && roomObj.value(QLatin1String("border")).isObject()) {
|
|
readJsonBorder(roomObj);
|
|
}
|
|
|
|
if (roomObj.contains(QLatin1String("environment")) && roomObj.value(QLatin1String("environment")).isDouble()) {
|
|
environment = roomObj.value(QLatin1String("environment")).toInt();
|
|
}
|
|
|
|
if (roomObj.contains(QLatin1String("highlight")) && roomObj.value(QLatin1String("highlight")).isObject()) {
|
|
const QJsonObject highlightObj{roomObj.value(QLatin1String("highlight")).toObject()};
|
|
readJsonHighlight(highlightObj);
|
|
}
|
|
|
|
if (roomObj.contains(QLatin1String("hash")) && roomObj.value(QLatin1String("hash")).isString()) {
|
|
const QString hashForRoomID{roomObj.value(QLatin1String("hash")).toString()};
|
|
if (!hashForRoomID.isEmpty()) {
|
|
mpRoomDB->hashToRoomID.insert(hashForRoomID, roomId);
|
|
mpRoomDB->roomIDToHash.insert(roomId, hashForRoomID);
|
|
}
|
|
}
|
|
|
|
if (readJsonExits(roomObj)) {
|
|
calcRoomDimensions();
|
|
}
|
|
|
|
readJsonExitStubs(roomObj);
|
|
|
|
return roomId;
|
|
}
|
|
|
|
// This inserts an array of exits into the provided QJsonObject container
|
|
// under the key "exits":
|
|
void TRoom::writeJsonExits(QJsonObject& obj) const
|
|
{
|
|
QJsonArray exitArray;
|
|
if (north > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_NORTH);
|
|
}
|
|
if (northeast > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_NORTHEAST);
|
|
}
|
|
if (northwest > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_NORTHWEST);
|
|
}
|
|
if (east > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_EAST);
|
|
}
|
|
if (west > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_WEST);
|
|
}
|
|
if (south > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_SOUTH);
|
|
}
|
|
if (southeast > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_SOUTHEAST);
|
|
}
|
|
if (southwest > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_SOUTHWEST);
|
|
}
|
|
if (up > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_UP);
|
|
}
|
|
if (down > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_DOWN);
|
|
}
|
|
if (in > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_IN);
|
|
}
|
|
if (out > 0) {
|
|
writeJsonNormalExit(exitArray, DIR_OUT);
|
|
}
|
|
QMapIterator<QString, int> itSpecialExit(mSpecialExits);
|
|
while (itSpecialExit.hasNext()) {
|
|
itSpecialExit.next();
|
|
writeJsonSpecialExit(exitArray, itSpecialExit.key(), itSpecialExit.value());
|
|
}
|
|
const QJsonValue exitsValue{exitArray};
|
|
obj[QLatin1String("exits")] = exitsValue;
|
|
}
|
|
|
|
bool TRoom::readJsonExits(const QJsonObject& obj)
|
|
{
|
|
const QJsonArray exitArray = obj.value(QLatin1String("exits")).toArray();
|
|
bool hasCustomExits = false;
|
|
for (const QJsonValue& exitValue : exitArray) {
|
|
const QJsonObject exitObj{exitValue.toObject()};
|
|
const QString dirString{exitObj.value(QLatin1String("name")).toString()};
|
|
const int dirCode = stringToDirCode(dirString);
|
|
if (dirCode != DIR_OTHER) {
|
|
if (readJsonNormalExit(exitObj, dirCode)) {
|
|
hasCustomExits = true;
|
|
}
|
|
} else {
|
|
if (readJsonSpecialExit(exitObj, dirString)) {
|
|
hasCustomExits = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return hasCustomExits;
|
|
}
|
|
|
|
// This inserts a newly constructed QJsonObject into the provided QJsonArray
|
|
// container for the specified normal exit:
|
|
void TRoom::writeJsonNormalExit(QJsonArray& array, const int dir) const
|
|
{
|
|
QJsonObject exitObj;
|
|
const QString directionString = dirCodeToString(dir);
|
|
const int exitId = getExit(dir);
|
|
const QString directionKey = dirCodeToShortString(dir);
|
|
// Skip any unreal exits:
|
|
if (exitId < 1) {
|
|
return;
|
|
}
|
|
|
|
const QJsonValue directionStringValue{directionString};
|
|
exitObj.insert(QLatin1String("name"), directionStringValue);
|
|
exitObj.insert(QLatin1String("exitId"), static_cast<double>(exitId));
|
|
|
|
// The second argument is used to look-up the right value for the door- so
|
|
// needs to be the "shortstring":
|
|
writeJsonDoor(exitObj, directionKey);
|
|
|
|
if (exitWeights.contains(directionKey)) {
|
|
exitObj.insert(QLatin1String("weight"), static_cast<double>(exitWeights.value(directionKey)));
|
|
}
|
|
|
|
if (exitLocks.contains(dir)) {
|
|
// We won't bother to include this item for unlocked exits but we will
|
|
// check that it is true when reading in case the file gets edited:
|
|
exitObj.insert(QLatin1String("locked"), true);
|
|
}
|
|
|
|
if (customLines.contains(directionKey)) {
|
|
writeJsonCustomExitLine(exitObj, directionKey);
|
|
}
|
|
const QJsonValue exitValue{exitObj};
|
|
array.append(exitValue);
|
|
}
|
|
|
|
bool TRoom::readJsonNormalExit(const QJsonObject& exitObj, const int dir)
|
|
{
|
|
const int exitRoomId = exitObj.value(QLatin1String("exitId")).toInt();
|
|
bool hasCustomExit = false;
|
|
if (exitRoomId < 1) {
|
|
qDebug().nospace().noquote() << "TRoom::readJsonNormalExit(...) INFO - when reading exits for room id: " << id << " the normal \"" << dirCodeToString(dir)
|
|
<< "\" exit had an invalid exit room id of: " << exitRoomId;
|
|
return hasCustomExit;
|
|
}
|
|
|
|
const QString shortString{dirCodeToShortString(dir)};
|
|
switch (dir) {
|
|
case DIR_NORTH:
|
|
north = exitRoomId;
|
|
break;
|
|
case DIR_NORTHEAST:
|
|
northeast = exitRoomId;
|
|
break;
|
|
case DIR_NORTHWEST:
|
|
northwest = exitRoomId;
|
|
break;
|
|
case DIR_EAST:
|
|
east = exitRoomId;
|
|
break;
|
|
case DIR_WEST:
|
|
west = exitRoomId;
|
|
break;
|
|
case DIR_SOUTH:
|
|
south = exitRoomId;
|
|
break;
|
|
case DIR_SOUTHEAST:
|
|
southeast = exitRoomId;
|
|
break;
|
|
case DIR_SOUTHWEST:
|
|
southwest = exitRoomId;
|
|
break;
|
|
case DIR_UP:
|
|
up = exitRoomId;
|
|
break;
|
|
case DIR_DOWN:
|
|
down = exitRoomId;
|
|
break;
|
|
case DIR_IN:
|
|
in = exitRoomId;
|
|
break;
|
|
case DIR_OUT:
|
|
out = exitRoomId;
|
|
break;
|
|
default:
|
|
Q_UNREACHABLE(); // Special exits should never have gotten into this method!
|
|
}
|
|
|
|
if (exitObj.contains(QLatin1String("weight")) && exitObj.value(QLatin1String("weight")).isDouble() && exitObj.value(QLatin1String("weight")).toInt() > 0) {
|
|
exitWeights.insert(shortString, exitObj.value(QLatin1String("weight")).toInt());
|
|
}
|
|
|
|
if (exitObj.contains(QLatin1String("locked")) && exitObj.value(QLatin1String("locked")).isBool() && exitObj.value(QLatin1String("locked")).toBool()) {
|
|
// We don't bother to include this item for unlocked exits but we will
|
|
// check that it is true when reading in case the file was edited:
|
|
exitLocks.append(dir);
|
|
}
|
|
|
|
if (exitObj.contains(QLatin1String("customLine")) && exitObj.value(QLatin1String("customLine")).isObject()) {
|
|
readJsonCustomExitLine(exitObj, shortString);
|
|
hasCustomExit = true;
|
|
}
|
|
|
|
if (exitObj.contains(QLatin1String("door")) && exitObj.value(QLatin1String("door")).isString()) {
|
|
readJsonDoor(exitObj, shortString);
|
|
}
|
|
|
|
return hasCustomExit;
|
|
}
|
|
|
|
void TRoom::writeJsonSpecialExit(QJsonArray& array, const QString& dir, const int exitId) const
|
|
{
|
|
QJsonObject exitObj;
|
|
const bool exitLocked = mSpecialExitLocks.contains(dir);
|
|
|
|
// Safety step to avoid insertion of any unreal exits:
|
|
if (exitId < 1 || dir.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
const QJsonValue dirValue{dir};
|
|
exitObj.insert(QLatin1String("name"), dirValue);
|
|
exitObj.insert(QLatin1String("exitId"), static_cast<double>(exitId));
|
|
|
|
writeJsonDoor(exitObj, dir);
|
|
|
|
if (exitWeights.contains(dir)) {
|
|
exitObj.insert(QLatin1String("weight"), static_cast<double>(exitWeights.value(dir)));
|
|
}
|
|
|
|
if (exitLocked) {
|
|
// We won't bother to include this item for unlocked exits but we will
|
|
// check that it is true when reading in case the file gets edited:
|
|
exitObj.insert(QLatin1String("locked"), true);
|
|
}
|
|
|
|
if (customLines.contains(dir)) {
|
|
writeJsonCustomExitLine(exitObj, dir);
|
|
}
|
|
|
|
const QJsonValue exitValue{exitObj};
|
|
array.append(exitValue);
|
|
}
|
|
|
|
bool TRoom::readJsonSpecialExit(const QJsonObject& exitObj, const QString& dir)
|
|
{
|
|
const int exitRoomId = exitObj.value(QLatin1String("exitId")).toInt();
|
|
bool hasCustomExit = false;
|
|
if (exitRoomId < 1) {
|
|
qDebug().nospace().noquote() << "TRoom::readJsonSpecialExit(...) INFO - when reading exits for room id: " << id << " the special \"" << dir
|
|
<< "\" exit had an invalid exit room id of: " << exitRoomId;
|
|
return hasCustomExit;
|
|
}
|
|
|
|
mSpecialExits.insert(dir, exitRoomId);
|
|
|
|
if (exitObj.contains(QLatin1String("weight")) && exitObj.value(QLatin1String("weight")).isDouble() && exitObj.value(QLatin1String("weight")).toInt() > 0) {
|
|
exitWeights.insert(dir, exitObj.value(QLatin1String("weight")).toInt());
|
|
}
|
|
|
|
if (exitObj.contains(QLatin1String("locked")) && exitObj.value(QLatin1String("locked")).isBool() && exitObj.value(QLatin1String("locked")).toBool()) {
|
|
// We don't bother to include this item for unlocked exits but we will
|
|
// check that it is true when reading in case the file was edited:
|
|
mSpecialExitLocks.insert(dir);
|
|
}
|
|
|
|
if (exitObj.contains(QLatin1String("customLine")) && exitObj.value(QLatin1String("customLine")).isObject()) {
|
|
readJsonCustomExitLine(exitObj, dir);
|
|
hasCustomExit = true;
|
|
}
|
|
|
|
if (exitObj.contains(QLatin1String("door")) && exitObj.value(QLatin1String("door")).isString()) {
|
|
readJsonDoor(exitObj, dir);
|
|
}
|
|
|
|
return hasCustomExit;
|
|
}
|
|
|
|
void TRoom::writeJsonDoor(QJsonObject& obj, const QString& key) const
|
|
{
|
|
QString door;
|
|
switch (doors.value(key)) {
|
|
// We won't bother to include this item for exits without doors but we
|
|
// will check that it is one of these values (and not "none") when
|
|
// reading in case the file gets edited:
|
|
case 1:
|
|
door = QLatin1String("open");
|
|
break;
|
|
case 2:
|
|
door = QLatin1String("closed");
|
|
break;
|
|
case 3:
|
|
door = QLatin1String("locked");
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
const QJsonValue doorValue{door};
|
|
obj.insert(QLatin1String("door"), doorValue);
|
|
}
|
|
|
|
// The first argument is an exit, special exit or stub QJsonObject, the second
|
|
// is a "shortstring" (for normal exit directions):
|
|
void TRoom::readJsonDoor(const QJsonObject& obj, const QString& dir)
|
|
{
|
|
if (!obj.contains(QLatin1String("door")) || !obj.value(QLatin1String("door")).isString()) {
|
|
return;
|
|
}
|
|
|
|
const QString doorString{obj.value(QLatin1String("door")).toString()};
|
|
if (doorString == QLatin1String("open")) {
|
|
doors.insert(dir, 1);
|
|
return;
|
|
}
|
|
if (doorString == QLatin1String("closed")) {
|
|
doors.insert(dir, 2);
|
|
return;
|
|
}
|
|
if (doorString == QLatin1String("locked")) {
|
|
doors.insert(dir, 3);
|
|
return;
|
|
}
|
|
if (Q_UNLIKELY(doorString == QLatin1String("none"))) {
|
|
return;
|
|
}
|
|
qCritical().nospace().noquote() << "TRoom::readJsonDoor(...) CRITICAL - a type of door: \"" << dir << "\" is not understood!";
|
|
Q_UNREACHABLE(); // No other string expected
|
|
}
|
|
|
|
// This tacks on extra details onto the calling exitObj if there IS a custom line:
|
|
void TRoom::writeJsonCustomExitLine(QJsonObject& exitObj, const QString& directionString) const
|
|
{
|
|
// Safety step to not insert anything for an empty custom line
|
|
if (!customLines.contains(directionString) || customLines.value(directionString).isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QJsonObject customLineObj;
|
|
QJsonArray customLinePointsArray;
|
|
for (const QPointF point : customLines.value(directionString)) {
|
|
QJsonArray customLinePointCoordinateArray;
|
|
customLinePointCoordinateArray.append(static_cast<double>(point.x()));
|
|
customLinePointCoordinateArray.append(static_cast<double>(point.y()));
|
|
// We might wish to consider storing a z in the future to accommodate 3D
|
|
// custom lines...!
|
|
const QJsonValue customLinePointCoordinatesValue{customLinePointCoordinateArray};
|
|
customLinePointsArray.append(customLinePointCoordinatesValue);
|
|
}
|
|
const QJsonValue customLineCoordinatesValue{customLinePointsArray};
|
|
customLineObj.insert(QLatin1String("coordinates"), customLineCoordinatesValue);
|
|
|
|
TMap::writeJsonColor(customLineObj, customLinesColor.value(directionString));
|
|
|
|
customLineObj.insert(QLatin1String("endsInArrow"), customLinesArrow.value(directionString));
|
|
|
|
QString lineStyle;
|
|
switch (customLinesStyle.value(directionString)) {
|
|
case Qt::DotLine:
|
|
lineStyle = QLatin1String("dot line");
|
|
break;
|
|
case Qt::DashDotLine:
|
|
lineStyle = QLatin1String("dash dot line");
|
|
break;
|
|
case Qt::DashDotDotLine:
|
|
lineStyle = QLatin1String("dash dot dot line");
|
|
break;
|
|
case Qt::DashLine:
|
|
lineStyle = QLatin1String("dash line");
|
|
break;
|
|
default: {
|
|
} // Use this (nothing) for Solid but will also convert anything else we do not handle to it as well
|
|
}
|
|
if (!lineStyle.isEmpty()) {
|
|
const QJsonValue customLineStyleValue{lineStyle};
|
|
customLineObj.insert(QLatin1String("style"), customLineStyleValue);
|
|
}
|
|
|
|
const QJsonValue customLineValue{customLineObj};
|
|
exitObj.insert(QLatin1String("customLine"), customLineValue);
|
|
}
|
|
|
|
void TRoom::readJsonCustomExitLine(const QJsonObject& exitObj, const QString& directionString)
|
|
{
|
|
const QJsonObject customLineObj{exitObj.value(QLatin1String("customLine")).toObject()};
|
|
|
|
// Safety step to not insert anything for an empty custom line
|
|
if (!customLineObj.contains(QLatin1String("coordinates")) || !customLineObj.value(QLatin1String("coordinates")).isArray()) {
|
|
return;
|
|
}
|
|
|
|
const QJsonArray customLinePointsArray = customLineObj.value(QLatin1String("coordinates")).toArray();
|
|
if (customLinePointsArray.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QList<QPointF> points;
|
|
for (const auto& coordinates : customLinePointsArray) {
|
|
const QJsonArray customLinePointCoordinateArray = coordinates.toArray();
|
|
if (customLinePointCoordinateArray.size() == 2 && customLinePointCoordinateArray.at(0).isDouble() && customLinePointCoordinateArray.at(1).isDouble()) {
|
|
const QPointF point{customLinePointCoordinateArray.at(0).toDouble(), customLinePointCoordinateArray.at(1).toDouble()};
|
|
|
|
// We might wish to consider if there is a z in the future to
|
|
// accommodate 3D custom lines...!
|
|
points.append(point);
|
|
}
|
|
}
|
|
customLines.insert(directionString, points);
|
|
|
|
customLinesColor.insert(directionString, TMap::readJsonColor(customLineObj));
|
|
|
|
if (customLineObj.contains(QLatin1String("endsInArrow")) && customLineObj.value(QLatin1String("endsInArrow")).isBool()) {
|
|
customLinesArrow.insert(directionString, customLineObj.value(QLatin1String("endsInArrow")).toBool());
|
|
} else {
|
|
customLinesArrow.insert(directionString, false);
|
|
}
|
|
|
|
if (customLineObj.contains(QLatin1String("style")) && customLineObj.value(QLatin1String("style")).isString()) {
|
|
const QString lineStyle{customLineObj.value(QLatin1String("style")).toString()};
|
|
if (lineStyle == QLatin1String("dash line")) {
|
|
customLinesStyle.insert(directionString, Qt::DashLine);
|
|
} else if (lineStyle == QLatin1String("dash dot dot line")) {
|
|
customLinesStyle.insert(directionString, Qt::DashDotDotLine);
|
|
} else if (lineStyle == QLatin1String("dash dot line")) {
|
|
customLinesStyle.insert(directionString, Qt::DashDotLine);
|
|
} else if (lineStyle == QLatin1String("dot line")) {
|
|
customLinesStyle.insert(directionString, Qt::DotLine);
|
|
} else {
|
|
customLinesStyle.insert(directionString, Qt::SolidLine);
|
|
}
|
|
} else {
|
|
customLinesStyle.insert(directionString, Qt::SolidLine);
|
|
}
|
|
}
|
|
|
|
void TRoom::writeJsonUserData(QJsonObject& obj) const
|
|
{
|
|
QJsonObject userDataObj;
|
|
if (userData.isEmpty()) {
|
|
// Skip creating a user data array if it will be empty:
|
|
return;
|
|
}
|
|
QMapIterator<QString, QString> itDataItem(userData);
|
|
while (itDataItem.hasNext()) {
|
|
itDataItem.next();
|
|
const QJsonValue userDataValue{itDataItem.value()};
|
|
userDataObj.insert(itDataItem.key(), userDataValue);
|
|
}
|
|
const QJsonValue userDatasValue{userDataObj};
|
|
obj.insert(QLatin1String("userData"), userDatasValue);
|
|
}
|
|
|
|
void TRoom::readJsonUserData(const QJsonObject& obj)
|
|
{
|
|
if (obj.isEmpty()) {
|
|
// Bail out immediately if there is nothing to do:
|
|
return;
|
|
}
|
|
|
|
for (auto& key : obj.keys()) {
|
|
if (obj.value(key).isString()) {
|
|
userData.insert(key, obj.value(key).toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void TRoom::writeJsonExitStubs(QJsonObject& obj) const
|
|
{
|
|
QJsonArray exitStubsArray;
|
|
if (exitStubs.isEmpty()) {
|
|
// Don't add an empty stub array
|
|
return;
|
|
}
|
|
|
|
// Given a forecast that we might eventually allow special exit stubs, use
|
|
// strings not numbers for the identifiers:
|
|
QList<QString> exitStubsList;
|
|
for (auto stub : exitStubs) {
|
|
exitStubsList.append(dirCodeToString(stub));
|
|
}
|
|
|
|
if (exitStubsList.count() > 1) {
|
|
std::sort(exitStubsList.begin(), exitStubsList.end());
|
|
}
|
|
|
|
for (const auto& stubName : exitStubsList) {
|
|
QJsonObject exitStubObj;
|
|
const QJsonValue stubNameValue{stubName};
|
|
exitStubObj.insert(QLatin1String("name"), stubNameValue);
|
|
writeJsonDoor(exitStubObj, stubName);
|
|
if (exitLocks.contains(stringToDirCode(stubName))) {
|
|
exitStubObj.insert(QLatin1String("locked"), true);
|
|
}
|
|
const QJsonValue exitStubValue{exitStubObj};
|
|
exitStubsArray.append(exitStubValue);
|
|
}
|
|
|
|
const QJsonValue exitStubsValues{exitStubsArray};
|
|
obj.insert(QLatin1String("stubExits"), exitStubsValues);
|
|
}
|
|
|
|
void TRoom::readJsonExitStubs(const QJsonObject& obj)
|
|
{
|
|
// There shouldn't be an empty stub array
|
|
if (!obj.contains(QLatin1String("stubExits")) || !obj.value(QLatin1String("stubExits")).isArray()) {
|
|
return;
|
|
}
|
|
const QJsonArray exitStubsArray = obj.value(QLatin1String("stubExits")).toArray();
|
|
if (exitStubsArray.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// Given a forecast that we might eventually allow special exit stubs, issue
|
|
// a warning if we detect such a thing in the current file:
|
|
for (const auto exitStubValue : exitStubsArray) {
|
|
const QJsonObject exitStubObj{exitStubValue.toObject()};
|
|
const QString direction{exitStubObj.value(QLatin1String("name")).toString()};
|
|
const int dir = stringToDirCode(direction);
|
|
QString doorKey;
|
|
if (dir != DIR_OTHER) {
|
|
doorKey = dirCodeToShortString(dir);
|
|
exitStubs.append(dir);
|
|
} else {
|
|
doorKey = direction;
|
|
qWarning().nospace().noquote() << "TRoom::readJsonExitStubs(...) WARNING - a special exit stub for the name/command: \"" << direction
|
|
<< "\" has been detected - but Mudlet does not currently support stubs in non-normal exit directions!";
|
|
continue;
|
|
}
|
|
|
|
// Will only get here for normal exit directions:
|
|
if (exitStubObj.contains(QLatin1String("door")) && exitStubObj.value(QLatin1String("door")).isString()) {
|
|
readJsonDoor(exitStubObj, doorKey);
|
|
}
|
|
|
|
if (exitStubObj.contains(QLatin1String("locked")) && exitStubObj.value(QLatin1String("locked")).isBool() && exitStubObj.value(QLatin1String("locked")).toBool()) {
|
|
exitLocks.append(dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Not currently used (it isn't saved in the binary format either) but reserved
|
|
// in case we do desire it - leaving the reader code in place so that if it
|
|
// does get used then no change to the format is needed:
|
|
void TRoom::writeJsonHighlight(QJsonObject& obj) const
|
|
{
|
|
const bool noColor = (highlightColor == scDefaultHighlightForeground) && (highlightColor2 == scDefaultHighlightBackground);
|
|
if (!highlight && qFuzzyCompare(1.0f + highlightRadius, 1.0f) && noColor) {
|
|
// The default case so no need to include it:
|
|
return;
|
|
}
|
|
|
|
QJsonObject highlightObj;
|
|
highlightObj.insert(QLatin1String("active"), highlight);
|
|
|
|
if (!noColor) {
|
|
QJsonArray highlightColorArray;
|
|
QJsonObject highlightColorFgObj;
|
|
QJsonObject highlightColorBgObj;
|
|
TMap::writeJsonColor(highlightColorFgObj, highlightColor);
|
|
TMap::writeJsonColor(highlightColorBgObj, highlightColor2);
|
|
const QJsonValue highlightColorFgValue{highlightColorFgObj};
|
|
const QJsonValue highlightColorBgValue{highlightColorBgObj};
|
|
highlightColorArray.append(highlightColorFgValue);
|
|
highlightColorArray.append(highlightColorBgValue);
|
|
const QJsonValue highlightColorValue{highlightColorArray};
|
|
highlightObj.insert(QLatin1String("colors"), highlightColorValue);
|
|
}
|
|
|
|
highlightObj.insert(QLatin1String("radius"), static_cast<double>(highlightRadius));
|
|
const QJsonValue highlightValue{highlightObj};
|
|
obj.insert(QLatin1String("highlight"), highlightValue);
|
|
}
|
|
|
|
void TRoom::readJsonHighlight(const QJsonObject& highlightObj)
|
|
{
|
|
if (highlightObj.contains(QLatin1String("active")) && highlightObj.value(QLatin1String("active")).isBool()) {
|
|
highlight = highlightObj.value(QLatin1String("active")).toBool();
|
|
}
|
|
|
|
if (highlightObj.contains(QLatin1String("colors")) && highlightObj.value(QLatin1String("colors")).isArray() && highlightObj.value(QLatin1String("colors")).toArray().size() == 2) {
|
|
const QJsonArray highlightColorArray = highlightObj.value(QLatin1String("colors")).toArray();
|
|
|
|
highlightColor = TMap::readJsonColor(highlightColorArray.at(0).toObject());
|
|
highlightColor2 = TMap::readJsonColor(highlightColorArray.at(1).toObject());
|
|
}
|
|
|
|
highlightRadius = highlightObj.value(QLatin1String("radius")).toDouble();
|
|
}
|
|
|
|
void TRoom::writeJsonSymbol(QJsonObject& roomObj) const
|
|
{
|
|
if (mSymbol.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QJsonObject symbolObj;
|
|
const QJsonValue symbolText{mSymbol};
|
|
symbolObj.insert(QLatin1String("text"), symbolText);
|
|
if (mSymbolColor.isValid()) {
|
|
TMap::writeJsonColor(symbolObj, mSymbolColor);
|
|
}
|
|
|
|
const QJsonValue symbolValue{symbolObj};
|
|
roomObj.insert(QLatin1String("symbol"), symbolValue);
|
|
}
|
|
|
|
void TRoom::readJsonSymbol(const QJsonObject& roomObj)
|
|
{
|
|
const QJsonObject symbolObj{roomObj.value(QLatin1String("symbol")).toObject()};
|
|
if (symbolObj.contains(QLatin1String("text")) && symbolObj.value(QLatin1String("text")).isString()) {
|
|
mSymbol = symbolObj.value(QLatin1String("text")).toString();
|
|
}
|
|
|
|
const QColor color = TMap::readJsonColor(symbolObj);
|
|
if (color.isValid()) {
|
|
mSymbolColor = color;
|
|
}
|
|
}
|
|
|
|
void TRoom::writeJsonBorder(QJsonObject& roomObj) const
|
|
{
|
|
QJsonObject borderObj;
|
|
|
|
if (mBorderColor.isValid()) {
|
|
TMap::writeJsonColor(borderObj, mBorderColor);
|
|
}
|
|
|
|
if (mBorderThickness > 0) {
|
|
borderObj.insert(QLatin1String("thickness"), static_cast<double>(mBorderThickness));
|
|
}
|
|
|
|
if (!borderObj.isEmpty()) {
|
|
const QJsonValue borderValue{borderObj};
|
|
roomObj.insert(QLatin1String("border"), borderValue);
|
|
}
|
|
}
|
|
|
|
void TRoom::readJsonBorder(const QJsonObject& roomObj)
|
|
{
|
|
const QJsonObject borderObj{roomObj.value(QLatin1String("border")).toObject()};
|
|
|
|
const QColor color = TMap::readJsonColor(borderObj);
|
|
if (color.isValid()) {
|
|
mBorderColor = color;
|
|
}
|
|
|
|
if (borderObj.contains(QLatin1String("thickness")) && borderObj.value(QLatin1String("thickness")).isDouble()) {
|
|
int thickness = borderObj.value(QLatin1String("thickness")).toInt();
|
|
if (thickness > 0 && thickness <= 10) {
|
|
mBorderThickness = thickness;
|
|
}
|
|
}
|
|
}
|