Mudlet  0
Mudclient
TRoomDB.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
3  * Copyright (C) 2014 by Ahmed Charles - acharles@outlook.com *
4  * Copyright (C) 2014-2018 by Stephen Lyons - slysven@virginmedia.com *
5  * *
6  * This program is free software; you can redistribute it and/or modify *
7  * it under the terms of the GNU General Public License as published by *
8  * the Free Software Foundation; either version 2 of the License, or *
9  * (at your option) any later version. *
10  * *
11  * This program is distributed in the hope that it will be useful, *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14  * GNU General Public License for more details. *
15  * *
16  * You should have received a copy of the GNU General Public License *
17  * along with this program; if not, write to the *
18  * Free Software Foundation, Inc., *
19  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
20  ***************************************************************************/
21 
22 #include "TRoomDB.h"
23 
24 #include "Host.h"
25 #include "TArea.h"
26 #include "mudlet.h"
27 
28 #include "pre_guard.h"
29 #include <QElapsedTimer>
30 #include <QRegularExpression>
31 #include "post_guard.h"
32 
33 
35 : mpMap( pMap )
36 , mpTempRoomDeletionSet( nullptr )
37 , mUnnamedAreaName( tr( "Unnamed Area" ) )
38 , mDefaultAreaName( tr( "Default Area" ) )
39 {
40  // Ensure the default area is created, the area/areaName items that get
41  // created here will get blown away when a map is loaded but that is expected...
43 }
44 
46 {
47  if (id < 0) {
48  return nullptr;
49  }
50  auto i = rooms.find(id);
51  if (i != rooms.end() && i.key() == id) {
52  return i.value();
53  }
54  return nullptr;
55 }
56 
57 bool TRoomDB::addRoom(int id)
58 {
59  if (!rooms.contains(id) && id > 0) {
60  rooms[id] = new TRoom(this);
61  rooms[id]->setId(id);
62  // there is no point to update the entranceMap here, as the room has no exit information
63  return true;
64  } else {
65  if (id <= 0) {
66  QString error = QString("illegal room id=%1. roomID must be > 0").arg(id);
67  mpMap->logError(error);
68  }
69  return false;
70  }
71 }
72 
73 bool TRoomDB::addRoom(int id, TRoom* pR, bool isMapLoading)
74 {
75  if (!rooms.contains(id) && id > 0 && pR) {
76  rooms[id] = pR;
77  pR->setId(id);
78  updateEntranceMap(pR, isMapLoading);
79  return true;
80  } else {
81  return false;
82  }
83 }
84 
86 {
87  QList<int> keyList = entranceMap.keys();
88  QList<int> valueList = entranceMap.values();
89  QList<uint> deleteEntries;
90  int index = valueList.indexOf(value);
91  while (index != -1) {
92  deleteEntries.append(index);
93  index = valueList.indexOf(value, index + 1);
94  }
95  for (int i = deleteEntries.size() - 1; i >= 0; --i) {
96  entranceMap.remove(keyList.at(deleteEntries.at(i)), valueList.at(deleteEntries.at(i)));
97  }
98 }
99 
100 void TRoomDB::deleteValuesFromEntranceMap(QSet<int>& valueSet)
101 {
102  QElapsedTimer timer;
103  timer.start();
104  QList<int> keyList = entranceMap.keys();
105  QList<int> valueList = entranceMap.values();
106  QList<uint> deleteEntries;
107  foreach (int roomId, valueSet) {
108  int index = valueList.indexOf(roomId);
109  while (index >= 0) {
110  deleteEntries.append(index);
111  index = valueList.indexOf(roomId, index + 1);
112  }
113  }
114  for (unsigned int entry : deleteEntries) {
115  entranceMap.remove(keyList.at(entry), valueList.at(entry));
116  }
117  qDebug() << "TRoomDB::deleteValuesFromEntranceMap() with a list of:" << valueSet.size() << "items, run time:" << timer.nsecsElapsed() * 1.0e-9 << "sec.";
118 }
119 
121 {
122  TRoom* pR = getRoom(id);
123  updateEntranceMap(pR);
124 }
125 
126 void TRoomDB::updateEntranceMap(TRoom* pR, bool isMapLoading)
127 {
128  static bool showDebug = false; // Enable this at runtime (set a breakpoint on it) for debugging!
129 
130  // entranceMap maps the room to rooms it has a viable exit to. So if room b and c both have
131  // an exit to room a, upon deleting room a we want a map that allows us to find
132  // room b and c efficiently.
133  // So we create a mapping like: {room_a: room_b, room_a: room_c}. This allows us to delete
134  // rooms and know which other rooms are impacted by this change in a single lookup.
135  if (pR) {
136  int id = pR->getId();
137  QHash<int, int> exits = pR->getExits();
138  QList<int> toExits = exits.keys();
139  QString values;
140  // to update this we need to iterate the entire entranceMap and remove invalid
141  // connections. I'm not sure if this is efficient for every update, and given
142  // that we check for rooms existance when the map is used, we'll deal with
143  // possible spurious exits for now.
144  // entranceMap.remove(id); // <== not what is wanted
145  // We need to remove all values == id NOT keys == id, so try and do that
146  // now. SlySven
147 
148  if (!isMapLoading) {
149  deleteValuesFromEntranceMap(id); // When LOADING a map, will never need to do this
150  }
151  for (int toExit : toExits) {
152  if (showDebug) {
153  values.append(QStringLiteral("%1,").arg(toExit));
154  }
155  if (!entranceMap.contains(toExit, id)) {
156  // entranceMap is a QMultiHash, so multiple, identical entries is
157  // more than possible - it was actually happening and making
158  // entranceMap get larger than needed...!
159  entranceMap.insert(toExit, id);
160  }
161  }
162  if (showDebug) {
163  if (!values.isEmpty()) {
164  values.chop(1);
165  }
166  if (values.isEmpty()) {
167  qDebug() << "TRoomDB::updateEntranceMap(TRoom * pR) called for room with id:" << id << ", it is not an Entrance for any Rooms.";
168  } else {
169  qDebug() << "TRoomDB::updateEntranceMap(TRoom * pR) called for room with id:" << id << ", it is an Entrance for Room(s):" << values << ".";
170  }
171  }
172  }
173 }
174 
175 // this is call by TRoom destructor only
177 {
178  static QMultiHash<int, int> _entranceMap; // Make it persistant - for multiple room deletions
179  static bool isBulkDelete = false;
180  // Gets set / reset by mpTempRoomDeletionSet being non-null, used to setup
181  // _entranceMap the first time around for multi-room deletions
182 
183  TRoom* pR = getRoom(id);
184  // This will FAIL during map deletion as TRoomDB::rooms has already been
185  // zapped, so can use to skip everything...
186  if (pR) {
187  if (mpTempRoomDeletionSet && mpTempRoomDeletionSet->size() > 1) { // We are deleting multiple rooms
188  if (!isBulkDelete) {
189  _entranceMap = entranceMap;
190  _entranceMap.detach(); // MUST take a deep copy of the data
191  isBulkDelete = true; // But only do it the first time for a bulk delete
192  }
193  } else { // We are deleting a single room
194  if (isBulkDelete) { // Last time was a bulk delete but it isn't one now
195  isBulkDelete = false;
196  }
197  _entranceMap.clear();
198  _entranceMap = entranceMap; // Refresh our local copy
199  _entranceMap.detach(); // MUST take a deep copy of the data
200  }
201 
202  // FIXME: make a proper exit controller so we don't need to do all these if statements
203  // Remove the links from the rooms entering this room
204  QMultiHash<int, int>::const_iterator i = _entranceMap.constFind(id);
205  // The removeAllSpecialExitsToRoom below modifies the entranceMap - and
206  // it is unsafe to modify (use copy operations on) something that an STL
207  // iterator is active on - see "Implicit sharing iterator problem" in
208  // "Container Class | Qt 5.x Core" - this is now avoid by taking a deep
209  // copy and iterating through that instead whilst modifying the original
210  while (i != entranceMap.cend() && i.key() == id) {
211  if (i.value() == id || (mpTempRoomDeletionSet && mpTempRoomDeletionSet->size() > 1 && mpTempRoomDeletionSet->contains(i.value()))) {
212  ++i;
213  continue; // Bypass rooms we know are also to be deleted
214  }
215  TRoom* r = getRoom(i.value());
216  if (r) {
217  if (r->getNorth() == id) {
218  r->setNorth(-1);
219  }
220  if (r->getNortheast() == id) {
221  r->setNortheast(-1);
222  }
223  if (r->getNorthwest() == id) {
224  r->setNorthwest(-1);
225  }
226  if (r->getEast() == id) {
227  r->setEast(-1);
228  }
229  if (r->getWest() == id) {
230  r->setWest(-1);
231  }
232  if (r->getSouth() == id) {
233  r->setSouth(-1);
234  }
235  if (r->getSoutheast() == id) {
236  r->setSoutheast(-1);
237  }
238  if (r->getSouthwest() == id) {
239  r->setSouthwest(-1);
240  }
241  if (r->getUp() == id) {
242  r->setUp(-1);
243  }
244  if (r->getDown() == id) {
245  r->setDown(-1);
246  }
247  if (r->getIn() == id) {
248  r->setIn(-1);
249  }
250  if (r->getOut() == id) {
251  r->setOut(-1);
252  }
254  }
255  ++i;
256  }
257  rooms.remove(id);
258  // FIXME: make hashTable a bimap
259  QList<QString> keyList = hashTable.keys();
260  QList<int> valueList = hashTable.values();
261  for (int i = 0; i < valueList.size(); i++) {
262  if (valueList[i] == id) {
263  hashTable.remove(keyList[i]);
264  }
265  }
266  int areaID = pR->getArea();
267  TArea* pA = getArea(areaID);
268  if (pA) {
269  pA->removeRoom(id);
270  }
271  if ((!mpTempRoomDeletionSet) || mpTempRoomDeletionSet->size() == 1) { // if NOT deleting multiple rooms
272  entranceMap.remove(id); // Only removes matching keys
273  deleteValuesFromEntranceMap(id); // Needed to remove matching values
274  }
275  // Because we clear the graph in initGraph which will be called
276  // if mMapGraphNeedsUpdate is true -- we don't need to
277  // remove the vertex using clear_vertex and remove_vertex here
278  mpMap->mMapGraphNeedsUpdate = true;
279  return true;
280  }
281  return false;
282 }
283 
285 {
286  if (rooms.contains(id) && id > 0) {
287  if (mpMap->mRoomIdHash.value(mpMap->mpHost->getName()) == id) {
288  // Now we store mRoomId for each profile, we must remove any where
289  // this room was used
290  QList<QString> profilesWithUserInThisRoom = mpMap->mRoomIdHash.keys(id);
291  foreach (QString key, profilesWithUserInThisRoom) {
292  mpMap->mRoomIdHash[key] = 0;
293  }
294  }
295  if (mpMap->mTargetID == id) {
296  mpMap->mTargetID = 0;
297  }
298  TRoom* pR = getRoom(id);
299  delete pR;
300  return true;
301  }
302  return false;
303 }
304 
305 void TRoomDB::removeRoom(QSet<int>& ids)
306 {
307  QElapsedTimer timer;
308  timer.start();
309  QSet<int> deletedRoomIds;
310  mpTempRoomDeletionSet = &ids; // Will activate "bulk room deletion" code
311  // When used by TLuaInterpreter::deleteArea()
312  // via removeArea(int) the list of rooms to
313  // delete - as suppplied by the reference
314  // type argument IS NOT CONSTANT - it is
315  // ALTERED by TArea::removeRoom( int room )
316  // for each room that is removed
317  quint64 roomcount = mpTempRoomDeletionSet->size();
318  while (!mpTempRoomDeletionSet->isEmpty()) {
319  int deleteRoomId = *(mpTempRoomDeletionSet->constBegin());
320  TRoom* pR = getRoom(deleteRoomId);
321  if (pR) {
322  deletedRoomIds.insert(deleteRoomId);
323  delete pR;
324  }
325  mpTempRoomDeletionSet->remove(deleteRoomId);
326  }
327  foreach (int deleteRoomId, deletedRoomIds) {
328  entranceMap.remove(deleteRoomId); // This has been deferred from __removeRoom()
329  }
330  deleteValuesFromEntranceMap(deletedRoomIds);
331  mpTempRoomDeletionSet->clear();
332  mpTempRoomDeletionSet = nullptr;
333  qDebug() << "TRoomDB::removeRoom(QList<int>) run time for" << roomcount << "rooms:" << timer.nsecsElapsed() * 1.0e-9 << "sec.";
334 }
335 
337 {
338  if (TArea* pA = areas.value(id)) {
339  if (!rooms.isEmpty()) {
340  // During map deletion rooms will already
341  // have been cleared so this would not
342  // be wanted to be done in that case.
343  removeRoom(pA->rooms);
344  }
345  // During map deletion areaNamesMap will
346  // already have been cleared !!!
347  areaNamesMap.remove(id);
348  // This means areas.clear() is not needed during map
349  // deletion
350  areas.remove(id);
351 
352  mpMap->mMapGraphNeedsUpdate = true;
353  return true;
354  } else if (areaNamesMap.contains(id)) {
355  // Handle corner case where the area name was created but not used
356  areaNamesMap.remove(id);
357  return true;
358  }
359  return false;
360 }
361 
363 {
364  if (areaNamesMap.values().contains(name)) {
365  return removeArea(areaNamesMap.key(name)); // i.e. call the removeArea(int) method
366  } else {
367  return false;
368  }
369 }
370 
372 {
373  if (!pA) {
374  qWarning() << "TRoomDB::removeArea(TArea *) Warning - attempt to remove an area with a NULL TArea pointer!";
375  return;
376  }
377 
378  int areaId = areas.key(pA, 0);
379  if (areaId == areas.key(pA, -1)) {
380  // By testing twice with different default keys to return if value NOT
381  // found, we can be certain we have an actual valid value
382  removeArea(areaId);
383  } else {
384  qWarning() << "TRoomDB::removeArea(TArea *) Warning - attempt to remove an area NOT in TRoomDB::areas!";
385  }
386 }
387 
389 {
390  return areas.key(pA);
391 }
392 
394 {
395  QElapsedTimer timer;
396  timer.start();
397  QHashIterator<int, TRoom*> it(rooms);
398  while (it.hasNext()) {
399  it.next();
400  int id = it.key();
401  TRoom* pR = getRoom(id);
402  if (!pR) {
403  continue;
404  }
405  TArea* pA = getArea(pR->getArea());
406  if (!pA) {
407  areas[pR->getArea()] = new TArea(mpMap, this);
408  }
409  }
410 
411  // if the area has been created without any rooms add the area ID
412  QMapIterator<int, QString> it2(areaNamesMap);
413  while (it2.hasNext()) {
414  it2.next();
415  int id = it2.key();
416  if (!areas.contains(id)) {
417  areas[id] = new TArea(mpMap, this);
418  }
419  }
420  qDebug() << "TRoomDB::buildAreas() run time:" << timer.nsecsElapsed() * 1.0e-9 << "sec.";
421 }
422 
423 
424 const QList<TRoom*> TRoomDB::getRoomPtrList()
425 {
426  return rooms.values();
427 }
428 
430 {
431  return rooms.keys();
432 }
433 
435 {
436  //area id of -1 is a room in the "void", 0 is a failure
437  if (id > 0 || id == -1) {
438  return areas.value(id, nullptr);
439  } else {
440  return nullptr;
441  }
442 }
443 
444 // Used by TMap::audit() - can detect and return areas with normally invalids Id (less than -1 or zero)!
445 TArea* TRoomDB::getRawArea(int id, bool* isValid = nullptr)
446 {
447  if (areas.contains(id)) {
448  if (isValid) {
449  *isValid = true;
450  }
451  return areas.value(id);
452  } else {
453  if (isValid) {
454  *isValid = false;
455  }
456  return nullptr;
457  }
458 }
459 
460 bool TRoomDB::setAreaName(int areaID, QString name)
461 {
462  if (areaID < 1) {
463  qWarning() << "TRoomDB::setAreaName((int)areaID, (QString)name): WARNING: Suspect area id:" << areaID << "supplied.";
464  return false;
465  }
466  if (name.isEmpty()) {
467  qWarning() << "TRoomDB::setAreaName((int)areaID, (QString)name): WARNING: Empty name supplied.";
468  return false;
469  } else if (areaNamesMap.values().count(name) > 0) {
470  // That name is already IN the areaNamesMap
471  if (areaNamesMap.value(areaID) == name) {
472  // The trivial case, the given areaID already IS that name
473  return true;
474  } else {
475  qWarning() << "TRoomDB::setAreaName((int)areaID, (QString)name): WARNING: Duplicate name supplied" << name << "- that is not permitted any longer!";
476  return false;
477  }
478  }
479  areaNamesMap[areaID] = name;
480  // This creates a NEW area name with given areaID if the ID was not
481  // previously used - but the TArea only gets created if the user manually
482  // creates it with TLuaInterpreter::addArea(areaId), OR moves a room to the
483  // area with either a Lua command or GUI action.
484  return true;
485 }
486 
487 bool TRoomDB::addArea(int id)
488 {
489  if (!areas.contains(id)) {
490  areas[id] = new TArea(mpMap, this);
491  if (!areaNamesMap.contains(id)) {
492  // Must provide a name for this new area
493  QString newAreaName = mUnnamedAreaName;
494  if (areaNamesMap.values().contains(newAreaName)) {
495  // We already have an "unnamed area"
496  uint deduplicateSuffix = 0;
497  do {
498  newAreaName = QStringLiteral("%1_%2").arg(mUnnamedAreaName).arg(++deduplicateSuffix, 3, 10, QLatin1Char('0'));
499  } while (areaNamesMap.values().contains(newAreaName));
500  }
501  areaNamesMap.insert(id, newAreaName);
502  }
503  return true;
504  } else {
505  QString error = tr("Area with ID=%1 already exists!").arg(id);
506  mpMap->logError(error);
507  return false;
508  }
509 }
510 
512 {
513  int id = 1;
514  while (areas.contains(id)) {
515  id++;
516  }
517  return id;
518 }
519 
521 {
522  // reject it if area name already exists or is empty
523  if (name.isEmpty()) {
524  QString error = tr("An Unnamed Area is (no longer) permitted!");
525  mpMap->logError(error);
526  return 0;
527  } else if (areaNamesMap.values().contains(name)) {
528  QString error = tr("An area called %1 already exists!").arg(name);
529  mpMap->logError(error);
530  return 0;
531  }
532 
533  int areaID = createNewAreaID();
534  if (addArea(areaID)) {
535  areaNamesMap[areaID] = name;
536  // This will overwrite the "Unnamed Area_###" that addArea( areaID )
537  // will generate - but that is fine.
538  return areaID;
539  } else {
540  return 0; //fail
541  }
542 }
543 
544 // this func is called by the xml map importer
545 // NOTE: we no longer accept duplicate IDs or duplicate area names
546 // duplicate definitions are ignored
547 // Unless the area name is empty, in which case we provide one!
548 bool TRoomDB::addArea(int id, QString name)
549 {
550  if (((!name.isEmpty()) && areaNamesMap.values().contains(name)) || areaNamesMap.keys().contains(id)) {
551  return false;
552  } else if (addArea(id)) {
553  // This will generate an "Unnamed Area_###" area name which we should
554  // overwrite only if we have a name!
555  if (!name.isEmpty()) {
556  areaNamesMap[id] = name;
557  }
558  return true;
559  } else {
560  return false;
561  }
562 }
563 
564 const QList<TArea*> TRoomDB::getAreaPtrList()
565 {
566  return areas.values();
567 }
568 
570 {
571  return areas.keys();
572 }
573 
574 /*
575  * Tasks to perform
576  * 1) Validate all room Ids and remap any problem ones ( <1 ), retaining
577  * remappings for fixups later on
578  * 2) Find all areas that the rooms demand
579  * 3) Find all areas that the areanames demand
580  * 4) Validate all existing area Ids and remap any problem one
581  * 5) Merge in area Ids that rooms and areanames demand, remapping any problem
582  * ones
583  * 6) If rooms have been remapped, fixup rooms' exits' to use the new room Ids
584  * 7) If areas have been remapped, fixup rooms to use new area Ids
585  * 8) Validate area::rooms - adding or removing roomId based on the rooms' idea
586  * of the area they should be in - not forgetting any remappings of area/room
587  * Ids..
588  * 9) Validate TRoom elements: exits, stubs (beware of duplicate QList<T>
589  * elements), doors (must have a real or stub in that direction), custom
590  * lines (no key for an normal or special exit that isn't there), locks
591  * (beware of duplicate QList<T> elements), room and exit weights (can only
592  * have a weight on an actual exit).
593  * 10) Validate TArea elements: zLevels (beware of duplicate QList<T> elements)
594  */
595 void TRoomDB::auditRooms(QHash<int, int>& roomRemapping, QHash<int, int>& areaRemapping)
596 {
597  QSet<int> validUsedRoomIds; // Used good ids (>= 1)
598  QSet<int> validUsedAreaIds; // As rooms
599 
600  // START OF TASK 1 & 2:
601  // Scan through all rooms and record their idea of which area they
602  // should be in. Note invalid room Ids those will be fixed up later...
603  QHash<int, int> roomAreaHash; // Key: room Id, Value: area it believes it should be in
604  QMultiHash<int, int> areaRoomMultiHash; // Key: areaId, ValueS: rooms that believe they belong there
605 
606  { // Block this code to limit scope of iterator
607  QMutableHashIterator<int, TRoom*> itRoom(rooms);
608  while (itRoom.hasNext()) {
609  itRoom.next();
610  TRoom* pR = itRoom.value();
611  if (!pR) {
612  if (mudlet::self()->showMapAuditErrors()) {
613  QString warnMsg = tr("[ WARN ] - Problem with data structure associated with room id: %1 - that\n"
614  "room's data has been lost so the id is now being deleted. This\n"
615  "suggests serious problems with the currently running version of\n"
616  "Mudlet - is your system running out of memory?")
617  .arg(itRoom.key());
618  mpMap->postMessage(warnMsg);
619  }
620  mpMap->appendRoomErrorMsg(itRoom.key(),
621  tr("[ WARN ] - Problem with data structure associated with this room."
622  " The room's data has been lost so the id is now being deleted."
623  " This suggests serious problems with the currently running version of Mudlet"
624  " - is your system running out of memory?"),
625  true);
626  itRoom.remove();
627  continue;
628  }
629  if (itRoom.key() >= 1) {
630  validUsedRoomIds.insert(itRoom.key());
631  } else {
632  roomRemapping.insert(itRoom.key(), itRoom.key());
633  //Store them for now, will assign new values when we have the
634  // set of good ones already used
635  }
636 
637  int areaId = pR->getArea();
638  areaRoomMultiHash.insert(areaId, itRoom.key());
639  roomAreaHash.insert(itRoom.key(), areaId);
640  }
641  }
642 
643  // Check for existance of all areas needed by rooms
644  QSet<int> areaIdSet = areaRoomMultiHash.keys().toSet();
645 
646  // START OF TASK 3
647  // Throw in the area Ids from the areaNamesMap:
648  areaIdSet.unite(areaNamesMap.keys().toSet());
649 
650  // And the area Ids used by the map labels:
651  areaIdSet.unite(mpMap->mapLabels.keys().toSet());
652 
653  // Check the set of area Ids against the ones we actually have:
654  QSetIterator<int> itUsedArea(areaIdSet);
655  // This is a later fix, as I forgot to handle wanted area ids that are +ve
656  // (and thus valid) but absent:
657  QList<int> missingAreasNeeded;
658  while (itUsedArea.hasNext()) {
659  int usedAreaId = itUsedArea.next();
660  if (usedAreaId < -1 || !usedAreaId) {
661  areaRemapping.insert(usedAreaId, usedAreaId); // Will find new value to use later
662  } else {
663  validUsedAreaIds.insert(usedAreaId);
664  }
665 
666  if (!areas.contains(usedAreaId)) {
667  if (mudlet::self()->showMapAuditErrors()) {
668  QString warnMsg = tr("[ ALERT ] - Area with id: %1 expected but not found, will be created.").arg(usedAreaId);
669  mpMap->postMessage(warnMsg);
670  }
671  mpMap->appendAreaErrorMsg(usedAreaId, tr("[ ALERT ] - Area with this id expected but not found, will be created."), true);
672  missingAreasNeeded.append(usedAreaId);
673  }
674  }
675 
676  // START OF TASK 4
677  // Check for any problem Id in original areas
678  QMapIterator<int, TArea*> itArea(areas);
679  while (itArea.hasNext()) {
680  itArea.next();
681  int areaId = itArea.key();
682  if (areaId < -1 || !areaId) {
683  areaRemapping.insert(areaId, areaId); // Will find new value to use later
684  } else {
685  validUsedAreaIds.insert(areaId);
686  }
687  }
688 
689  // START OF TASK 5.0 (previously omitted) - add in areas that we have an id
690  // for but no actual TArea, at this point:
691  // * id WILL be in validUsedAreaIds
692  // * id WILL NOT be in areaRemapping, but represents a id number that cannot be
693  // remapped as a new area id
694  // * id MAY be in TRoomDB::areaNamesMap
695  // * id MAY be used by rooms which will have to be added to it at some point
696  // * WILL NOT be in TRoomDB::areas - as that is the source of the error this
697  // bit of the task being addressed here is fixing
698  if (!missingAreasNeeded.isEmpty()) {
699  if (mudlet::self()->showMapAuditErrors()) {
700  QString alertMsg = tr("[ ALERT ] - %n area(s) detected as missing in map: adding it/them in.\n"
701  " Look for further messsages related to the rooms that are supposed\n"
702  " to be in this/these area(s)...",
703  "Making use of %n to allow quantity dependent message form 8-) !",
704  missingAreasNeeded.count());
705  mpMap->postMessage(alertMsg);
706  }
707  mpMap->appendErrorMsgWithNoLf(tr("[ ALERT ] - %n area(s) detected as missing in map: adding it/them in.\n"
708  " Look for further messsages related to the rooms that is/are supposed to\n"
709  " be in this/these area(s)...",
710  "Making use of %n to allow quantity dependent message form 8-) !",
711  missingAreasNeeded.count()),
712  true);
713 
714  QString infoMsg;
715  if (mudlet::self()->showMapAuditErrors()) {
716  infoMsg = tr("[ INFO ] - The missing area(s) are now called:\n"
717  "(ID) ==> \"name\"",
718  "Making use of %n to allow quantity dependent message form 8-) !",
719  missingAreasNeeded.count());
720  }
721 
722  if (missingAreasNeeded.count() > 1) {
723  // Sort the ids so that the reporting is ordered, which could be
724  // helpful if there is a large number of faults
725  std::sort(missingAreasNeeded.begin(), missingAreasNeeded.end());
726  }
727 
728  for (int newAreaId : missingAreasNeeded) {
729  // This will create a new "Default" area name if there is not one
730  // already for this id - and we do not anticipate that it could ever
731  // fail and return false...
732  addArea(newAreaId);
733  infoMsg.append(QStringLiteral("\n%1 ==> \"%2\"").arg(QString::number(newAreaId), areaNamesMap.value(newAreaId)));
734  }
735 
736  // Didn't really needed to be done, but as we have finished with it
737  // now, clearing it may make tracking the overall processes going on
738  // in the debugger a little clearer...
739  missingAreasNeeded.clear();
740 
741  if (mudlet::self()->showMapAuditErrors()) {
742  mpMap->postMessage(infoMsg);
743  }
744  mpMap->appendErrorMsg(infoMsg, true);
745  }
746 
747  // START OF TASK 5.1
748  // Now process problem areaIds
749  if (!areaRemapping.isEmpty()) {
750  if (mudlet::self()->showMapAuditErrors()) {
751  QString alertMsg = tr("[ ALERT ] - Bad, (less than +1 and not the reserved -1) area ids found (count: %1)\n"
752  "in map, now working out what new id numbers to use...")
753  .arg(areaRemapping.count());
754  mpMap->postMessage(alertMsg);
755  }
756  mpMap->appendErrorMsg(tr("[ ALERT ] - Bad, (less than +1 and not the reserved -1) area ids found (count: %1) in map!"
757  " Look for further messsages related to this for each affected area ...")
758  .arg(areaRemapping.count()),
759  true);
760 
761  QString infoMsg;
762  if (mudlet::self()->showMapAuditErrors()) {
763  infoMsg = tr("[ INFO ] - The renumbered area ids will be:\n"
764  "Old ==> New");
765  }
766 
767  QMutableHashIterator<int, int> itRemappedArea(areaRemapping);
768  while (itRemappedArea.hasNext()) {
769  itRemappedArea.next();
770  int faultyAreaId = itRemappedArea.key();
771  int replacementAreaId = 0;
772  do {
773  ; // No-op, increment done in test
774  } while (areas.contains(++replacementAreaId));
775  // Insert replacement value into hash
776  itRemappedArea.setValue(replacementAreaId);
777  if (mudlet::self()->showMapAuditErrors()) {
778  infoMsg.append(QStringLiteral("\n%1 ==> %2").arg(QString::number(faultyAreaId), QString::number(replacementAreaId)));
779  }
780 
781  mpMap->appendAreaErrorMsg(faultyAreaId, tr("[ INFO ] - The area with this bad id was renumbered to: %1.").arg(replacementAreaId), true);
782  mpMap->appendAreaErrorMsg(replacementAreaId, tr("[ INFO ] - This area was renumbered from the bad id: %1.").arg(faultyAreaId), true);
783 
784  TArea* pA = nullptr;
785  if (areas.contains(faultyAreaId)) {
786  pA = areas.take(faultyAreaId);
787  } else {
788  pA = new TArea(mpMap, this);
789  }
790  if (areaNamesMap.contains(faultyAreaId)) {
791  QString areaName = areaNamesMap.value(faultyAreaId);
792  areaNamesMap.remove(faultyAreaId);
793  areaNamesMap.insert(replacementAreaId, areaName);
794  } else {
795  // I think this is unlikely but better provide code to cover it
796  // if it does arise that we need a new area but do not have a
797  // provided name
798  QString newAreaName = mUnnamedAreaName;
799  if (areaNamesMap.values().contains(newAreaName)) {
800  // We already have an "unnamed area"
801  uint deduplicateSuffix = 0;
802  do {
803  newAreaName = QStringLiteral("%1_%2").arg(mUnnamedAreaName).arg(++deduplicateSuffix, 3, 10, QLatin1Char('0'));
804  } while (areaNamesMap.values().contains(newAreaName));
805  }
806  areaNamesMap.insert(replacementAreaId, newAreaName);
807  }
808  pA->mUserData.insert(QStringLiteral("audit.remapped_id"), QString::number(faultyAreaId));
809  validUsedAreaIds.insert(replacementAreaId);
810  areas.insert(replacementAreaId, pA);
811 
812  // Fixup map labels as well
813  if (mpMap->mapLabels.contains(faultyAreaId)) {
814  QMap<qint32, TMapLabel> areaMapLabels = mpMap->mapLabels.take(faultyAreaId);
815  mpMap->mapLabels.insert(replacementAreaId, areaMapLabels);
816  }
817 
818  pA->mIsDirty = true;
819  }
820  if (mudlet::self()->showMapAuditErrors()) {
821  mpMap->postMessage(infoMsg);
822  }
823  } else {
824  if (mudlet::self()->showMapAuditErrors()) {
825  QString infoMsg = tr("[ INFO ] - Area id numbering is satisfactory.");
826  mpMap->postMessage(infoMsg);
827  }
828  mpMap->appendErrorMsg(tr("[ INFO ] - Area id numbering is satisfactory."), false);
829  }
830  // END OF TASK 2,3,4,5 - all needed areas exist and remap details are in
831  // areaRemapping - still need to update rooms and areaNames and mapLabels
832 
833  // Now complete TASK 1 - find the new room Ids to use
834  if (!roomRemapping.isEmpty()) {
835  if (mudlet::self()->showMapAuditErrors()) {
836  QString alertMsg = tr("[ ALERT ] - Bad, (less than +1) room ids found (count: %1) in map, now working\n"
837  "out what new id numbers to use.")
838  .arg(roomRemapping.count());
839  mpMap->postMessage(alertMsg);
840  }
841  mpMap->appendErrorMsg(tr("[ ALERT ] - Bad, (less than +1) room ids found (count: %1) in map!"
842  " Look for further messsages related to this for each affected room ...")
843  .arg(roomRemapping.count()),
844  true);
845 
846  QString infoMsg;
847  if (mudlet::self()->showMapAuditErrors()) {
848  infoMsg = tr("[ INFO ] - The renumbered rooms will be:\n");
849  }
850  QMutableHashIterator<int, int> itRenumberedRoomId(roomRemapping);
851  while (itRenumberedRoomId.hasNext()) {
852  itRenumberedRoomId.next();
853  unsigned int newRoomId = 0;
854  do {
855  ; // Noop - needed increment is done in test condition!
856  } while (validUsedRoomIds.contains(++newRoomId));
857 
858  itRenumberedRoomId.setValue(newRoomId); // Update the QHash
859  validUsedRoomIds.insert(newRoomId);
860  if (mudlet::self()->showMapAuditErrors()) {
861  infoMsg.append(QStringLiteral("%1 ==> %2").arg(QString::number(itRenumberedRoomId.key()), QString::number(itRenumberedRoomId.value())));
862  }
863 
864  mpMap->appendRoomErrorMsg(itRenumberedRoomId.key(), tr("[ INFO ] - This room with the bad id was renumbered to: %1.").arg(itRenumberedRoomId.value()), true);
865  mpMap->appendRoomErrorMsg(itRenumberedRoomId.value(), tr("[ INFO ] - This room was renumbered from the bad id: %1.").arg(itRenumberedRoomId.key()), true);
866  }
867  if (mudlet::self()->showMapAuditErrors()) {
868  mpMap->postMessage(infoMsg);
869  }
870 
871  QSet<TRoom*> holdingSet;
872  { // Block this code to limit scope of iterator
873  QMutableHashIterator<int, TRoom*> itRoom(rooms);
874  // Although this is "mutable" we can only changed the VALUE not the KEY
875  // Also we cannot directly change the collection being iterated over -
876  // though we can REMOVE items via the iterator - so pull the
877  // affected TRoom instances out of the Hash, renumber them and then
878  // hold onto them before reinserting them after we have finished
879  // this iteration
880  while (itRoom.hasNext()) {
881  itRoom.next();
882  TRoom* pR = itRoom.value();
883  if (roomRemapping.contains(itRoom.key())) {
884  pR->userData.insert(QStringLiteral("audit.remapped_id"), QString::number(itRoom.key()));
885  pR->setId(roomRemapping.value(itRoom.key()));
886  itRoom.remove();
887  holdingSet.insert(pR);
888  }
889  }
890  }
891 
892  // Now stuff the modified values back in under the new key values
893  QSetIterator<TRoom*> itModifiedRoom(holdingSet);
894  while (itModifiedRoom.hasNext()) {
895  TRoom* pR = itModifiedRoom.next();
896  int newRoomId = pR->getId();
897  rooms.insert(newRoomId, pR);
898  }
899  } else {
900  if (mudlet::self()->showMapAuditErrors()) {
901  QString infoMsg = tr("[ INFO ] - Room id numbering is satisfactory.");
902  mpMap->postMessage(infoMsg);
903  }
904  mpMap->appendErrorMsg(tr("[ INFO ] - Room id numbering is satisfactory."), false);
905  }
906  // END OF TASK 1
907 
908  // START OF TASK 6,7 & 9
909  { // Block the following code to limit the scope of the itRoom iterator
910  QMutableHashIterator<int, TRoom*> itRoom(rooms);
911  while (itRoom.hasNext()) {
912  itRoom.next();
913  TRoom* pR = itRoom.value();
914 
915  // Purges any duplicates that a QList structure DOES permit, but a QSet does NOT:
916  // Exit stubs:
917  unsigned int _listCount = pR->exitStubs.count();
918  QSet<int> _set = pR->exitStubs.toSet();
919  if (_set.count() < _listCount) {
920  if (mudlet::self()->showMapAuditErrors()) {
921  QString infoMsg = tr("[ INFO ] - Duplicate exit stub identifiers found in room id: %1, this is an\n"
922  "anomaly but has been cleaned up easily.")
923  .arg(itRoom.key());
924  mpMap->postMessage(infoMsg);
925  }
926  mpMap->appendRoomErrorMsg(itRoom.key(), tr("[ INFO ] - Duplicate exit stub identifiers found in room, this is an anomaly but has been cleaned up easily."), false);
927  }
928  pR->exitStubs = _set.toList();
929 
930  // Exit locks:
931  _listCount = pR->exitLocks.count();
932  _set = pR->exitLocks.toSet();
933  if (_set.count() < _listCount) {
934  if (mudlet::self()->showMapAuditErrors()) {
935  QString infoMsg = tr("[ INFO ] - Duplicate exit lock identifiers found in room id: %1, this is an\n"
936  "anomaly but has been cleaned up easily.")
937  .arg(itRoom.key());
938  mpMap->postMessage(infoMsg);
939  }
940  mpMap->appendRoomErrorMsg(itRoom.key(), tr("[ INFO ] - Duplicate exit lock identifiers found in room, this is an anomaly but has been cleaned up easily."), false);
941  }
942  pR->exitLocks = _set.toList();
943 
944  // TASK 9 IS DONE INSIDE THIS METHOD:
945  pR->audit(roomRemapping, areaRemapping);
946  }
947  }
948  // END OF TASK 6,7 & 9
949 
950  // START TASK 8
951  {
952  QMapIterator<int, TArea*> itArea(areas);
953  while (itArea.hasNext()) {
954  itArea.next();
955  TArea* pA = itArea.value();
956  QSet<int> replacementRoomsSet;
957  { // Block code to limit scope of iterator, find and pull out renumbered rooms
958  QMutableSetIterator<int> itAreaRoom(pA->rooms);
959  if (!roomRemapping.isEmpty()) {
960  while (itAreaRoom.hasNext()) {
961  int originalRoomId = itAreaRoom.next();
962  if (roomRemapping.contains(originalRoomId)) {
963  itAreaRoom.remove();
964  replacementRoomsSet.insert(roomRemapping.value(originalRoomId));
965  }
966  }
967  }
968  }
969  // Merge back in the renumbered rooms
970  if (!replacementRoomsSet.isEmpty()) {
971  pA->rooms.unite(replacementRoomsSet);
972  }
973 
974  // Now compare pA->rooms to areaRoomMultiHash.values( itArea.key() )
975  QSet<int> foundRooms = areaRoomMultiHash.values(itArea.key()).toSet();
976  QSetIterator<int> itFoundRoom(foundRooms);
977  // Original form of code which was slower because the two sets of rooms were
978  // compared TWICE:
979  // extraRooms = pA->rooms;
980  // extraRooms.subtract( foundRooms );
981  // missingRooms = foundRooms;
982  // missingRooms.subtract( pA->rooms );
983 
984  // Revised code that only makes one pass
985  QSet<int> extraRooms(pA->rooms); //Take common rooms from here so that any left are the "extras"
986  extraRooms.detach(); // We'll need a deep copy so might as well explicitly say so to make the copy
987  QSet<int> missingRooms; // Add rooms not in pA->room to here
988  int checkRoom;
989  while (itFoundRoom.hasNext()) {
990  checkRoom = itFoundRoom.next();
991  if (pA->rooms.contains(checkRoom)) {
992  extraRooms.remove(checkRoom);
993  } else {
994  missingRooms.insert(checkRoom);
995  }
996  }
997 
998  // Report differences:
999  if (!missingRooms.isEmpty()) {
1000  QStringList roomList;
1001  QList<int> missingRoomsList = missingRooms.toList();
1002  if (missingRoomsList.size() > 1) {
1003  // The on-screen listing are clearer if we sort the rooms
1004  std::sort(missingRoomsList.begin(), missingRoomsList.end());
1005  }
1006  QListIterator<int> itMissingRoom(missingRoomsList);
1007  while (itMissingRoom.hasNext()) {
1008  int missingRoomId = itMissingRoom.next();
1009  roomList.append(QString::number(missingRoomId));
1010  mpMap->appendRoomErrorMsg(missingRoomId,
1011  tr("[ INFO ] - This room claims to be in area id: %1, but that did not have a record of it."
1012  " The area has been updated to include this room.")
1013  .arg(itArea.key()),
1014  true);
1015  }
1016  if (mudlet::self()->showMapAuditErrors()) {
1017  QString infoMsg = tr("[ INFO ] - In area with id: %1 there were %2 rooms missing from those it\n"
1018  "should be recording as possessing, they were:\n%3\nthey have been added.")
1019  .arg(itArea.key())
1020  .arg(missingRooms.count())
1021  .arg(roomList.join(QStringLiteral(", ")));
1022  mpMap->postMessage(infoMsg);
1023  }
1024  mpMap->appendAreaErrorMsg(itArea.key(),
1025  tr("[ INFO ] - In this area there were %1 rooms missing from those it should be recorded as possessing."
1026  " They are: %2."
1027  " They have been added.")
1028  .arg(missingRooms.count())
1029  .arg(roomList.join(QStringLiteral(", "))),
1030  true);
1031 
1032  pA->mIsDirty = true;
1033  }
1034 
1035  if (!extraRooms.isEmpty()) {
1036  QStringList roomList;
1037  QList<int> extraRoomsList = extraRooms.toList();
1038  if (extraRoomsList.size() > 1) {
1039  std::sort(extraRoomsList.begin(), extraRoomsList.end());
1040  }
1041  QListIterator<int> itExtraRoom(extraRoomsList);
1042  while (itExtraRoom.hasNext()) {
1043  int extraRoomId = itExtraRoom.next();
1044  roomList.append(QString::number(extraRoomId));
1045  mpMap->appendRoomErrorMsg(extraRoomId,
1046  tr("[ INFO ] - This room was claimed by area id: %1, but it does not belong there."
1047  " The area has been updated to not include this room.")
1048  .arg(itArea.key()),
1049  true);
1050  }
1051  if (mudlet::self()->showMapAuditErrors()) {
1052  QString infoMsg = tr("[ INFO ] - In area with id: %1 there were %2 extra rooms compared to those it\n"
1053  "should be recording as possessing, they were:\n%3\nthey have been removed.")
1054  .arg(itArea.key())
1055  .arg(extraRooms.count())
1056  .arg(roomList.join(QStringLiteral(", ")));
1057  mpMap->postMessage(infoMsg);
1058  }
1059  mpMap->appendAreaErrorMsg(itArea.key(),
1060  tr("[ INFO ] - In this area there were %1 extra rooms that it should not be recorded as possessing."
1061  " They were: %2."
1062  " They have been removed.")
1063  .arg(extraRooms.count())
1064  .arg(roomList.join(QStringLiteral(", "))),
1065  true);
1066  pA->mIsDirty = true;
1067  }
1068  pA->rooms = foundRooms;
1069  }
1070  }
1071  // END OF TASK 8
1072 }
1073 
1075 {
1076  QElapsedTimer timer;
1077  timer.start();
1078  QList<TRoom*> rPtrL = getRoomPtrList();
1079  rooms.clear(); // Prevents any further use of TRoomDB::getRoom(int) !!!
1080  entranceMap.clear();
1081  areaNamesMap.clear();
1082  hashTable.clear();
1083  for (auto room : rPtrL) {
1084  delete room; // Uses the internally held value of the room Id
1085  // (TRoom::id) to call TRoomDB::__removeRoom(id)
1086  }
1087  // assert( rooms.size() == 0 ); // Pointless as rooms.clear() will have achieved the test condition
1088 
1089  QList<TArea*> areaList = getAreaPtrList();
1090  for (auto area : areaList) {
1091  delete area;
1092  }
1093  assert(areas.empty());
1094  // Must now reinsert areaId -1 name = "Default Area"
1096  qDebug() << "TRoomDB::clearMapDB() run time:" << timer.nsecsElapsed() * 1.0e-9 << "sec.";
1097 }
1098 
1099 void TRoomDB::restoreAreaMap(QDataStream& ifs)
1100 {
1101  QMap<int, QString> areaNamesMapWithPossibleEmptyOrDuplicateItems;
1102  ifs >> areaNamesMapWithPossibleEmptyOrDuplicateItems;
1103  areaNamesMap.clear(); // Following code assumes areaNamesMap is empty but
1104  // under some situations this has not been the case...
1105 
1106  // Validate names: name nameless areas and rename duplicates
1107  QMultiMap<QString, QString> renamedMap; // For warning message, holds renamed area map
1108  QMapIterator<int, QString> itArea = areaNamesMapWithPossibleEmptyOrDuplicateItems;
1109  bool isMatchingSuffixAlreadyPresent = false;
1110  bool isEmptyAreaNamePresent = false;
1111  while (itArea.hasNext()) {
1112  itArea.next();
1113  QString nonEmptyAreaName;
1114  if (itArea.value().isEmpty()) {
1115  isEmptyAreaNamePresent = true;
1116  nonEmptyAreaName = mUnnamedAreaName;
1117  // Will trip following if more than one
1118  } else {
1119  nonEmptyAreaName = itArea.value();
1120  }
1121  if (areaNamesMap.values().contains(nonEmptyAreaName)) {
1122  // Oh dear, we have a duplicate
1123  if (nonEmptyAreaName.contains(QRegularExpression(QStringLiteral(R"(_\d\d\d$)")))) {
1124  // the areaName already is of form "something_###" where # is a
1125  // digit, have to strip that off and remember so warning message
1126  // can include advice on this change
1127  isMatchingSuffixAlreadyPresent = true;
1128  nonEmptyAreaName.chop(4); // Take off existing suffix
1129  }
1130  uint deduplicateSuffix = 0;
1131  QString replacementName;
1132  do {
1133  replacementName = QStringLiteral("%1_%2").arg(nonEmptyAreaName).arg(++deduplicateSuffix, 3, 10, QLatin1Char('0'));
1134  } while (areaNamesMap.values().contains(replacementName));
1135  if ((!itArea.value().isEmpty()) && (!renamedMap.contains(itArea.value()))) {
1136  // if the renamedMap does not contain the first, unaltered value
1137  // that a subsequent match has been found for, then include it
1138  // in the data so the user can see the UNCHANGED one as well
1139  // Only have to do this once for each duplicate area name, hence
1140  // the test conditions above
1141  renamedMap.insert(itArea.value(), itArea.value());
1142  }
1143  renamedMap.insert(itArea.value(), replacementName);
1144  areaNamesMap.insert(itArea.key(), replacementName);
1145  } else {
1146  if (itArea.value().isEmpty()) {
1147  renamedMap.insert(itArea.value(), nonEmptyAreaName);
1148  }
1149  areaNamesMap.insert(itArea.key(), nonEmptyAreaName);
1150  }
1151  }
1152  if (!renamedMap.empty() || isEmptyAreaNamePresent) {
1153  QString alertText;
1154  QString informativeText;
1155  QString extraTextForMatchingSuffixAlreadyUsed;
1156  QString detailText;
1157  if (isMatchingSuffixAlreadyPresent) {
1158  extraTextForMatchingSuffixAlreadyUsed = tr(
1159  R"(It has been detected that "_###" form suffixes have already been used, for simplicity in the renaming algorithm these will have been removed and possibly changed as Mudlet sorts this matter out, if a number assigned in this way <b>is</b> important to you, you can change it back, provided you rename the area that has been allocated the suffix that was wanted first...!</p>)");
1160  }
1161  if (!renamedMap.empty()) {
1162  detailText = tr("[ OK ] - The changes made are:\n"
1163  "(ID) \"old name\" ==> \"new name\"\n");
1164  QMapIterator<QString, QString> itRemappedNames = renamedMap;
1165  itRemappedNames.toBack();
1166  // Seems to look better if we iterate through backwards!
1167  while (itRemappedNames.hasPrevious()) {
1168  itRemappedNames.previous();
1169  QString oldName = itRemappedNames.key().isEmpty() ? tr("<nothing>") : itRemappedNames.key();
1170  detailText.append(QStringLiteral("(%1) \"%2\" ==> \"%3\"\n").arg(areaNamesMap.key(itRemappedNames.value())).arg(oldName, itRemappedNames.value()));
1171  mpMap->appendAreaErrorMsg(areaNamesMap.key(itRemappedNames.value()),
1172  tr(R"([ INFO ] - Area name changed to prevent duplicates or unnamed ones; old name: "%1", new name: "%2".)").arg(oldName, itRemappedNames.value()),
1173  true);
1174  ;
1175  }
1176  detailText.chop(1); // Trim last "\n" off
1177  }
1178  if (!renamedMap.empty() && isEmptyAreaNamePresent) {
1179  // At least one unnamed area and at least one duplicate area name
1180  // - may be the same items
1181  alertText = tr("[ ALERT ] - Empty and duplicate area names detected in Map file!");
1182  informativeText = tr("[ INFO ] - Due to some situations not being checked in the past, Mudlet had\n"
1183  "allowed the map to have more than one area with the same or no name.\n"
1184  "These make some things confusing and are now disallowed.\n"
1185  " To resolve these cases, an area without a name here (or created in\n"
1186  "the future) will automatically be assigned the name \"%1\".\n"
1187  " Duplicated area names will cause all but the first encountered one\n"
1188  "to gain a \"_###\" style suffix where each \"###\" is an increasing\n"
1189  "number; you may wish to change these, perhaps by replacing them with\n"
1190  "a \"(sub-area name)\" but it is entirely up to you how you do this,\n"
1191  "other then you will not be able to set one area's name to that of\n"
1192  "another that exists at the time.\n"
1193  " If there were more than one area without a name then all but the\n"
1194  "first will also gain a suffix in this manner.\n"
1195  "%2").arg(mUnnamedAreaName, extraTextForMatchingSuffixAlreadyUsed);
1196  } else if (!renamedMap.empty()) {
1197  // Duplicates but no unnnamed area
1198  alertText = tr("[ ALERT ] - Duplicate area names detected in the Map file!");
1199  informativeText = tr("[ INFO ] - Due to some situations not being checked in the past, Mudlet had\n"
1200  "allowed the user to have more than one area with the same name.\n"
1201  "These make some things confusing and are now disallowed.\n"
1202  " Duplicated area names will cause all but the first encountered one\n"
1203  "to gain a \"_###\" style suffix where each \"###\" is an increasing\n"
1204  "number; you may wish to change these, perhaps by replacing them with\n"
1205  "a \"(sub-area name)\" but it is entirely up to you how you do this,\n"
1206  "other then you will not be able to set one area's name to that of\n"
1207  "another that exists at the time.\n"
1208  " If there were more than one area without a name then all but the\n"
1209  "first will also gain a suffix in this manner.\n"
1210  "%1)")
1211  .arg(extraTextForMatchingSuffixAlreadyUsed);
1212  } else {
1213  // A single unnamed area found
1214  alertText = tr("[ ALERT ] - An empty area name was detected in the Map file!");
1215  // Use OK for this one because it is the last part and indicates the
1216  // sucessful end of something, whereas INFO is an intermediate step
1217  informativeText = tr("[ OK ] - Due to some situations not being checked in the past, Mudlet had\n"
1218  "allowed the map to have an area with no name. This can make some\n"
1219  "things confusing and is now disallowed.\n"
1220  " To resolve this case, the area without a name here (or one created\n"
1221  "in the future) will automatically be assigned the name \"%1\".\n"
1222  " If this happens more then once the duplication of area names will\n"
1223  "cause all but the first encountered one to gain a \"_###\" style\n"
1224  "suffix where each \"###\" is an increasing number; you may wish to\n"
1225  "change these, perhaps by adding more meaningful area names but it is\n"
1226  "entirely up to you what is used, other then you will not be able to\n"
1227  "set one area's name to that of another that exists at the time.")
1228  .arg(mUnnamedAreaName);
1229  }
1230  mpMap->mpHost->postMessage(alertText);
1231  mpMap->appendErrorMsgWithNoLf(alertText, true);
1232  mpMap->mpHost->postMessage(informativeText);
1233  mpMap->appendErrorMsgWithNoLf(informativeText, true);
1234  if (!detailText.isEmpty()) {
1235  mpMap->mpHost->postMessage(detailText);
1236  }
1237  }
1238 
1239  if (!areaNamesMap.contains(-1)) {
1240  areaNamesMap.insert(-1, mDefaultAreaName);
1241  QString defaultAreaNameInsertionMsg = tr("[ INFO ] - Default (reset) area name (for rooms that have not been assigned to an\n"
1242  "area) not found, adding \"%1\" against the reserved -1 id.")
1243  .arg(mDefaultAreaName);
1244  mpMap->mpHost->postMessage(defaultAreaNameInsertionMsg);
1245  mpMap->appendErrorMsgWithNoLf(defaultAreaNameInsertionMsg, false);
1246  }
1247 }
1248 
1249 void TRoomDB::restoreSingleArea(int areaID, TArea* pA)
1250 {
1251  areas[areaID] = pA;
1252 }
1253 
1255 {
1256  addRoom(i, pT, true);
1257 }
1258 
1259 // Used by XMLimport to fix TArea::rooms data after import
1260 void TRoomDB::setAreaRooms(const int areaId, const QSet<int>& roomIds)
1261 {
1262  TArea* pA = areas.value(areaId);
1263  if (!pA) {
1264  qWarning() << "TRoomDB::setAreaRooms(" << areaId << ", ... ) ERROR - Non-existant area Id given...!";
1265  return;
1266  }
1267 
1268  QSetIterator<int> itAreaRoom(roomIds);
1269  while (itAreaRoom.hasNext()) {
1270  pA->addRoom(itAreaRoom.next());
1271  }
1272 
1273  pA->calcSpan(); // The area extents will need recalculation after adding the rooms
1274 }
void setOut(int id)
Definition: TRoom.h:113
void appendRoomErrorMsg(int, QString, bool isToSetFileViewingRecommended=false)
Definition: TMap.cpp:2050
const QList< TRoom * > getRoomPtrList()
Definition: TRoomDB.cpp:424
QList< int > getAreaIDList()
Definition: TRoomDB.cpp:569
bool __removeRoom(int id)
Definition: TRoomDB.cpp:176
int getSouthwest()
Definition: TRoom.h:98
void setUp(int id)
Definition: TRoom.h:107
void logError(QString &msg)
Definition: TMap.cpp:182
QMultiHash< int, int > entranceMap
Definition: TRoomDB.h:95
bool mMapGraphNeedsUpdate
Definition: TMap.h:187
bool showMapAuditErrors() const
Definition: mudlet.h:272
int createNewAreaID()
Definition: TRoomDB.cpp:511
int getUp()
Definition: TRoom.h:106
int getNortheast()
Definition: TRoom.h:94
int getIn()
Definition: TRoom.h:110
void appendErrorMsgWithNoLf(QString, bool isToSetFileViewingRecommended=false)
Definition: TMap.cpp:2068
int getDown()
Definition: TRoom.h:108
int getId()
Definition: TRoom.h:114
int getArea()
Definition: TRoom.h:115
void restoreAreaMap(QDataStream &)
Definition: TRoomDB.cpp:1099
void updateEntranceMap(TRoom *, bool isMapLoading=false)
Definition: TRoomDB.cpp:126
Q_DECLARE_TR_FUNCTIONS(TRoomDB) public TRoom * getRoom(int id)
Definition: TRoomDB.cpp:45
int getWest()
Definition: TRoom.h:102
void setNorthwest(int id)
Definition: TRoom.h:93
QPointer< Host > mpHost
Definition: TMap.h:156
QMap< QString, QString > mUserData
Definition: TArea.h:87
int getNorth()
Definition: TRoom.h:90
bool setAreaName(int areaID, QString name)
Definition: TRoomDB.cpp:460
void audit(QHash< int, int >, QHash< int, int >)
Definition: TRoom.cpp:906
friend class TRoom
Definition: TRoomDB.h:103
void setSouth(int id)
Definition: TRoom.h:97
void setWest(int id)
Definition: TRoom.h:103
void calcSpan()
Definition: TArea.cpp:378
void setNorth(int id)
Definition: TRoom.h:91
void setIn(int id)
Definition: TRoom.h:111
void setNortheast(int id)
Definition: TRoom.h:95
void auditRooms(QHash< int, int > &, QHash< int, int > &)
Definition: TRoomDB.cpp:595
bool removeArea(int id)
Definition: TRoomDB.cpp:336
QList< int > exitLocks
Definition: TRoom.h:151
QString mDefaultAreaName
Definition: TRoomDB.h:101
QSet< int > rooms
Definition: TArea.h:64
void setDown(int id)
Definition: TRoom.h:109
QList< int > exitStubs
Definition: TRoom.h:149
TArea * getRawArea(int, bool *)
Definition: TRoomDB.cpp:445
const QList< TArea * > getAreaPtrList()
Definition: TRoomDB.cpp:564
bool removeRoom(int)
Definition: TRoomDB.cpp:284
QList< int > getRoomIDList()
Definition: TRoomDB.cpp:429
void restoreSingleRoom(int, TRoom *)
Definition: TRoomDB.cpp:1254
QSet< int > * mpTempRoomDeletionSet
Definition: TRoomDB.h:99
void buildAreas()
Definition: TRoomDB.cpp:393
int getSoutheast()
Definition: TRoom.h:100
Definition: TMap.h:79
void setId(int)
Definition: TRoom.cpp:230
static mudlet * self()
Definition: mudlet.cpp:126
void setAreaRooms(int, const QSet< int > &)
Definition: TRoomDB.cpp:1260
void postMessage(QString text)
Definition: TMap.cpp:2033
void setEast(int id)
Definition: TRoom.h:105
TArea * getArea(int id)
Definition: TRoomDB.cpp:434
Definition: TArea.h:38
int getOut()
Definition: TRoom.h:112
int getEast()
Definition: TRoom.h:104
QHash< QString, int > mRoomIdHash
Definition: TMap.h:160
TMap * mpMap
Definition: TRoomDB.h:98
QMap< int, QString > areaNamesMap
Definition: TRoomDB.h:97
void restoreSingleArea(int, TArea *)
Definition: TRoomDB.cpp:1249
void clearMapDB()
Definition: TRoomDB.cpp:1074
void addRoom(int id)
Definition: TArea.cpp:363
void appendAreaErrorMsg(int, QString, bool isToSetFileViewingRecommended=false)
Definition: TMap.cpp:2056
Definition: TRoom.h:56
bool addRoom(int id)
Definition: TRoomDB.cpp:57
void setSouthwest(int id)
Definition: TRoom.h:99
void removeAllSpecialExitsToRoom(int _id)
Definition: TRoom.cpp:664
QMap< QString, QString > userData
Definition: TRoom.h:150
QMap< QString, int > hashTable
Definition: TRoomDB.h:84
bool addArea(int id)
Definition: TRoomDB.cpp:487
TRoomDB()=default
Definition: TRoomDB.cpp:34
QMap< qint32, QMap< qint32, TMapLabel > > mapLabels
Definition: TMap.h:189
bool mIsDirty
Definition: TArea.h:86
void setSoutheast(int id)
Definition: TRoom.h:101
int getSouth()
Definition: TRoom.h:96
QHash< int, int > getExits()
Definition: TRoom.cpp:422
QMap< int, TArea * > areas
Definition: TRoomDB.h:96
int getAreaID(TArea *pA)
Definition: TRoomDB.cpp:388
QHash< int, TRoom * > rooms
Definition: TRoomDB.h:94
int getNorthwest()
Definition: TRoom.h:92
void removeRoom(int, bool isToDeferAreaRelatedRecalculations=false)
Definition: TArea.cpp:479
QString mUnnamedAreaName
Definition: TRoomDB.h:100
void deleteValuesFromEntranceMap(int)
Definition: TRoomDB.cpp:85
void appendErrorMsg(QString, bool isToSetFileViewingRecommended=false)
Definition: TMap.cpp:2062
int mTargetID
Definition: TMap.h:166