//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Remere's Map Editor is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//////////////////////////////////////////////////////////////////////
#include "main.h"
#include "gui.h"
#include "map.h"
#include "client_assets.h"
Map::Map() :
BaseMap(),
width(512),
height(512),
houses(*this),
has_changed(false),
unnamed(false),
waypoints(*this),
zones(*this) {
// Earliest version possible
// Caller is responsible for converting us to proper version
mapVersion.otbm = MAP_OTBM_1;
}
Map::~Map() {
////
}
bool Map::open(const std::string file) {
if (file == filename) {
return true; // Do not reopen ourselves!
}
tilecount = 0;
IOMapOTBM maploader(getVersion());
bool success = maploader.loadMap(*this, wxstr(file));
mapVersion = maploader.version;
warnings = maploader.getWarnings();
if (!success) {
error = maploader.getError();
return false;
}
has_changed = false;
wxFileName fn = wxstr(file);
filename = fn.GetFullPath().mb_str(wxConvUTF8);
name = fn.GetFullName().mb_str(wxConvUTF8);
// convert(getReplacementMapClassic(), true);
#if 0 // This will create a replacement map out of one of SO's template files
std::ofstream out("templateshit.cpp");
out << "\tConversionMap replacement_map;\n";
int count = 0;
out << "\tstd::vector veckey, vecval;\n\n";
for(int x = 20; ; x += 2) {
int y = 22;
Tile* old = getTile(x, y, rme::MapGroundLayer);
if(old) {
y -= 2;
Tile* new_ = getTile(x, y, rme::MapGroundLayer);
if(new_) {
if(old->ground || old->items.size()) {
out << "\tvecval.clear();\n";
if(new_->ground)
out << "\tvecval.push_back(" << new_->ground->getID() << ");\n";
for(ItemVector::iterator iter = new_->items.begin(); iter != new_->items.end(); ++iter)
out << "\tvecval.push_back(" << (*iter)->getID() << ");\n";
if(old->ground && old->items.empty()) // Single item
out << "\treplacement_map.stm[" << old->ground->getID() << "] = vecval;\n\n";
else if(old->ground == nullptr && old->items.size() == 1) // Single item
out << "\treplacement_map.stm[" << old->items.front()->getID() << "] = vecval;\n\n";
else {
// Many items
out << "\tveckey.clear();\n";
if(old->ground)
out << "\tveckey.push_back(" << old->ground->getID() << ");\n";
for(ItemVector::iterator iter = old->items.begin(); iter != old->items.end(); ++iter)
out << "\tveckey.push_back(" << (*iter)->getID() << ");\n";
out << "\tstd::sort(veckey.begin(), veckey.end());\n";
out << "\treplacement_map.mtm[veckey] = vecval;\n\n";
}
}
}
} else {
break;
}
}
out.close();
#endif
return true;
}
bool Map::convert(MapVersion to, bool showdialog) {
mapVersion = to;
return true;
}
bool Map::convert(const ConversionMap &rm, bool showdialog) {
if (showdialog) {
g_gui.CreateLoadBar("Converting map ...");
}
uint64_t tiles_done = 0;
std::vector id_list;
// std::ofstream conversions("converted_items.txt");
for (MapIterator miter = begin(); miter != end(); ++miter) {
Tile* tile = (*miter)->get();
ASSERT(tile);
if (tile->size() == 0) {
continue;
}
// id_list try MTM conversion
id_list.clear();
if (tile->ground) {
id_list.push_back(tile->ground->getID());
}
for (ItemVector::const_iterator item_iter = tile->items.begin(); item_iter != tile->items.end(); ++item_iter) {
if ((*item_iter)->isBorder()) {
id_list.push_back((*item_iter)->getID());
}
}
std::sort(id_list.begin(), id_list.end());
ConversionMap::MTM::const_iterator cfmtm = rm.mtm.end();
while (id_list.size()) {
cfmtm = rm.mtm.find(id_list);
if (cfmtm != rm.mtm.end()) {
break;
}
id_list.pop_back();
}
// Keep track of how many items have been inserted at the bottom
size_t inserted_items = 0;
if (cfmtm != rm.mtm.end()) {
const std::vector &v = cfmtm->first;
if (tile->ground && std::find(v.begin(), v.end(), tile->ground->getID()) != v.end()) {
delete tile->ground;
tile->ground = nullptr;
}
for (ItemVector::iterator item_iter = tile->items.begin(); item_iter != tile->items.end();) {
if (std::find(v.begin(), v.end(), (*item_iter)->getID()) != v.end()) {
delete *item_iter;
item_iter = tile->items.erase(item_iter);
} else {
++item_iter;
}
}
const std::vector &new_items = cfmtm->second;
for (std::vector::const_iterator iit = new_items.begin(); iit != new_items.end(); ++iit) {
Item* item = Item::Create(*iit);
if (item->isGroundTile()) {
tile->ground = item;
} else {
tile->items.insert(tile->items.begin(), item);
++inserted_items;
}
}
}
if (tile->ground) {
ConversionMap::STM::const_iterator cfstm = rm.stm.find(tile->ground->getID());
if (cfstm != rm.stm.end()) {
uint16_t aid = tile->ground->getActionID();
uint16_t uid = tile->ground->getUniqueID();
delete tile->ground;
tile->ground = nullptr;
const std::vector &v = cfstm->second;
// conversions << "Converted " << tile->getX() << ":" << tile->getY() << ":" << tile->getZ() << " " << id << " -> ";
for (std::vector::const_iterator iit = v.begin(); iit != v.end(); ++iit) {
Item* item = Item::Create(*iit);
// conversions << *iit << " ";
if (item->isGroundTile()) {
item->setActionID(aid);
item->setUniqueID(uid);
tile->addItem(item);
} else {
tile->items.insert(tile->items.begin(), item);
++inserted_items;
}
}
// conversions << std::endl;
}
}
for (ItemVector::iterator replace_item_iter = tile->items.begin() + inserted_items; replace_item_iter != tile->items.end();) {
uint16_t id = (*replace_item_iter)->getID();
ConversionMap::STM::const_iterator cf = rm.stm.find(id);
if (cf != rm.stm.end()) {
// uint16_t aid = (*replace_item_iter)->getActionID();
// uint16_t uid = (*replace_item_iter)->getUniqueID();
delete *replace_item_iter;
replace_item_iter = tile->items.erase(replace_item_iter);
const std::vector &v = cf->second;
for (std::vector::const_iterator iit = v.begin(); iit != v.end(); ++iit) {
replace_item_iter = tile->items.insert(replace_item_iter, Item::Create(*iit));
// conversions << "Converted " << tile->getX() << ":" << tile->getY() << ":" << tile->getZ() << " " << id << " -> " << *iit << std::endl;
++replace_item_iter;
}
} else {
++replace_item_iter;
}
}
++tiles_done;
if (showdialog && tiles_done % 0x10000 == 0) {
g_gui.SetLoadDone(int(tiles_done / double(getTileCount()) * 100.0));
}
}
if (showdialog) {
g_gui.DestroyLoadBar();
}
return true;
}
void Map::cleanInvalidTiles(bool showdialog) {
if (showdialog) {
g_gui.CreateLoadBar("Removing invalid tiles...");
}
uint64_t tiles_done = 0;
for (MapIterator miter = begin(); miter != end(); ++miter) {
Tile* tile = (*miter)->get();
ASSERT(tile);
if (tile->size() == 0) {
continue;
}
for (ItemVector::iterator item_iter = tile->items.begin(); item_iter != tile->items.end();) {
if (g_items.isValidID((*item_iter)->getID())) {
++item_iter;
} else {
delete *item_iter;
item_iter = tile->items.erase(item_iter);
}
}
++tiles_done;
if (showdialog && tiles_done % 0x10000 == 0) {
g_gui.SetLoadDone(int(tiles_done / double(getTileCount()) * 100.0));
}
}
if (showdialog) {
g_gui.DestroyLoadBar();
}
}
void Map::cleanDeletedZones(bool showdialog) {
if (showdialog) {
g_gui.CreateLoadBar("Removing deleted zones...");
}
uint64_t tiles_done = 0;
for (MapIterator miter = begin(); miter != end(); ++miter) {
Tile* tile = (*miter)->get();
ASSERT(tile);
if (tile->size() == 0) {
continue;
}
for (auto iter = tile->zones.begin(); iter != tile->zones.end();) {
if (zones.hasZone(*iter)) {
++iter;
} else {
iter = tile->zones.erase(iter);
}
}
++tiles_done;
if (showdialog && tiles_done % 0x10000 == 0) {
g_gui.SetLoadDone(int(tiles_done / double(getTileCount()) * 100.0));
}
}
if (showdialog) {
g_gui.DestroyLoadBar();
}
}
Position Map::getZonePosition(unsigned int zoneId) {
Position pos;
for (MapIterator miter = begin(); miter != end(); ++miter) {
Tile* tile = (*miter)->get();
ASSERT(tile);
if (tile->size() == 0) {
continue;
}
if (tile->zones.find(zoneId) != tile->zones.end()) {
pos = tile->getPosition();
break;
}
}
return pos;
}
bool Map::doChange() {
bool doupdate = !has_changed;
has_changed = true;
return doupdate;
}
bool Map::clearChanges() {
bool doupdate = has_changed;
has_changed = false;
return doupdate;
}
void Map::setWidth(int new_width) {
if (new_width > 65000) {
width = 65000;
} else if (new_width < 64) {
width = 64;
} else {
width = new_width;
}
}
void Map::setHeight(int new_height) {
if (new_height > 65000) {
height = 65000;
} else if (new_height < 64) {
height = 64;
} else {
height = new_height;
}
}
void Map::setMapDescription(const std::string &new_description) {
description = new_description;
}
void Map::setHouseFilename(const std::string &new_housefile) {
housefile = new_housefile;
unnamed = false;
}
void Map::setZoneFilename(const std::string &new_zonefile) {
zonefile = new_zonefile;
unnamed = false;
}
void Map::setSpawnMonsterFilename(const std::string &new_spawnmonsterfile) {
spawnmonsterfile = new_spawnmonsterfile;
unnamed = false;
}
void Map::setSpawnNpcFilename(const std::string &new_spawnnpcfile) {
spawnnpcfile = new_spawnnpcfile;
unnamed = false;
}
bool Map::addSpawnMonster(Tile* tile) {
SpawnMonster* spawnMonster = tile->spawnMonster;
if (spawnMonster) {
int z = tile->getZ();
int start_x = tile->getX() - spawnMonster->getSize();
int start_y = tile->getY() - spawnMonster->getSize();
int end_x = tile->getX() + spawnMonster->getSize();
int end_y = tile->getY() + spawnMonster->getSize();
for (int y = start_y; y <= end_y; ++y) {
for (int x = start_x; x <= end_x; ++x) {
TileLocation* ctile_loc = createTileL(x, y, z);
ctile_loc->increaseSpawnCount();
}
}
spawnsMonster.addSpawnMonster(tile);
return true;
}
return false;
}
void Map::removeSpawnMonsterInternal(Tile* tile) {
SpawnMonster* spawnMonster = tile->spawnMonster;
ASSERT(spawnMonster);
int z = tile->getZ();
int start_x = tile->getX() - spawnMonster->getSize();
int start_y = tile->getY() - spawnMonster->getSize();
int end_x = tile->getX() + spawnMonster->getSize();
int end_y = tile->getY() + spawnMonster->getSize();
for (int y = start_y; y <= end_y; ++y) {
for (int x = start_x; x <= end_x; ++x) {
TileLocation* ctile_loc = getTileL(x, y, z);
if (ctile_loc != nullptr && ctile_loc->getSpawnMonsterCount() > 0) {
ctile_loc->decreaseSpawnMonsterCount();
}
}
}
}
void Map::removeSpawnMonster(Tile* tile) {
if (tile->spawnMonster) {
removeSpawnMonsterInternal(tile);
spawnsMonster.removeSpawnMonster(tile);
}
}
SpawnMonsterList Map::getSpawnMonsterList(const Tile* tile) const {
SpawnMonsterList list;
if (!tile) {
return list;
}
const TileLocation* location = tile->getLocation();
if (!location || location->getSpawnMonsterCount() == 0) {
return list;
}
uint32_t found = 0;
if (tile->spawnMonster) {
++found;
list.push_back(tile->spawnMonster);
}
// Scans the border tiles in an expanding square around the original spawn
const Position &position = tile->getPosition();
int start_x = position.x - 1;
int end_x = position.x + 1;
int start_y = position.y - 1;
int end_y = position.y + 1;
while (found != location->getSpawnMonsterCount()) {
for (int x = start_x; x <= end_x; ++x) {
const Tile* start_tile = getTile(x, start_y, position.z);
if (start_tile && start_tile->spawnMonster) {
list.push_back(start_tile->spawnMonster);
++found;
}
const Tile* end_tile = getTile(x, end_y, position.z);
if (end_tile && end_tile->spawnMonster) {
list.push_back(end_tile->spawnMonster);
++found;
}
}
for (int y = start_y + 1; y < end_y; ++y) {
const Tile* start_tile = getTile(start_x, y, position.z);
if (start_tile && start_tile->spawnMonster) {
list.push_back(start_tile->spawnMonster);
++found;
}
const Tile* end_tile = getTile(end_x, y, position.z);
if (end_tile && end_tile->spawnMonster) {
list.push_back(end_tile->spawnMonster);
++found;
}
}
for (int y = start_y + 1; y < end_y; ++y) {
const Tile* start_tile = getTile(start_x, y, position.z);
if (start_tile && start_tile->spawnMonster) {
list.push_back(start_tile->spawnMonster);
++found;
}
const Tile* end_tile = getTile(end_x, y, position.z);
if (end_tile && end_tile->spawnMonster) {
list.push_back(end_tile->spawnMonster);
++found;
}
}
--start_x;
--start_y;
++end_x;
++end_y;
}
return list;
}
SpawnMonsterList Map::getSpawnMonsterList(const Position &position) const {
const Tile* tile = getTile(position);
return getSpawnMonsterList(tile);
}
SpawnMonsterList Map::getSpawnMonsterList(int x, int y, int z) const {
const Tile* tile = getTile(x, y, z);
return getSpawnMonsterList(tile);
}
bool Map::addSpawnNpc(Tile* tile) {
SpawnNpc* spawnNpc = tile->spawnNpc;
if (spawnNpc) {
int z = tile->getZ();
int start_x = tile->getX() - spawnNpc->getSize();
int start_y = tile->getY() - spawnNpc->getSize();
int end_x = tile->getX() + spawnNpc->getSize();
int end_y = tile->getY() + spawnNpc->getSize();
for (int y = start_y; y <= end_y; ++y) {
for (int x = start_x; x <= end_x; ++x) {
TileLocation* ctile_loc = createTileL(x, y, z);
ctile_loc->increaseSpawnNpcCount();
}
}
spawnsNpc.addSpawnNpc(tile);
return true;
}
return false;
}
void Map::removeSpawnNpcInternal(Tile* tile) {
SpawnNpc* spawnNpc = tile->spawnNpc;
ASSERT(spawnNpc);
int z = tile->getZ();
int start_x = tile->getX() - spawnNpc->getSize();
int start_y = tile->getY() - spawnNpc->getSize();
int end_x = tile->getX() + spawnNpc->getSize();
int end_y = tile->getY() + spawnNpc->getSize();
for (int y = start_y; y <= end_y; ++y) {
for (int x = start_x; x <= end_x; ++x) {
TileLocation* ctile_loc = getTileL(x, y, z);
if (ctile_loc != nullptr && ctile_loc->getSpawnNpcCount() > 0) {
ctile_loc->decreaseSpawnNpcCount();
}
}
}
}
void Map::removeSpawnNpc(Tile* tile) {
if (tile->spawnNpc) {
removeSpawnNpcInternal(tile);
spawnsNpc.removeSpawnNpc(tile);
}
}
SpawnNpcList Map::getSpawnNpcList(const Tile* tile) const {
SpawnNpcList listNpc;
if (!tile) {
return listNpc;
}
const TileLocation* location = tile->getLocation();
if (!location || location->getSpawnNpcCount() == 0) {
return listNpc;
}
uint32_t found = 0;
if (tile->spawnNpc) {
++found;
listNpc.push_back(tile->spawnNpc);
}
// Scans the border tiles in an expanding square around the original spawn
const Position &position = tile->getPosition();
int start_x = position.x - 1;
int end_x = position.x + 1;
int start_y = position.y - 1;
int end_y = position.y + 1;
while (found != location->getSpawnNpcCount()) {
for (int x = start_x; x <= end_x; ++x) {
const Tile* start_tile = getTile(x, start_y, position.z);
if (start_tile && start_tile->spawnNpc) {
listNpc.push_back(start_tile->spawnNpc);
++found;
}
const Tile* end_tile = getTile(x, end_y, position.z);
if (end_tile && end_tile->spawnNpc) {
listNpc.push_back(end_tile->spawnNpc);
++found;
}
}
for (int y = start_y + 1; y < end_y; ++y) {
const Tile* start_tile = getTile(start_x, y, position.z);
if (start_tile && start_tile->spawnNpc) {
listNpc.push_back(start_tile->spawnNpc);
++found;
}
const Tile* end_tile = getTile(end_x, y, position.z);
if (end_tile && end_tile->spawnNpc) {
listNpc.push_back(end_tile->spawnNpc);
++found;
}
}
--start_x;
--start_y;
++end_x;
++end_y;
}
return listNpc;
}
SpawnNpcList Map::getSpawnNpcList(const Position &position) const {
const Tile* tile = getTile(position);
return getSpawnNpcList(tile);
}
SpawnNpcList Map::getSpawnNpcList(int x, int y, int z) const {
const Tile* tile = getTile(x, y, z);
return getSpawnNpcList(tile);
}
bool Map::exportMinimap(FileName filename, int floor /*= rme::MapGroundLayer*/, bool displaydialog) {
uint8_t* pic = nullptr;
try {
int min_x = 0x10000, min_y = 0x10000;
int max_x = 0x00000, max_y = 0x00000;
if (size() == 0) {
return true;
}
uint32_t minimap_colors[256];
for (int i = 0; i < 256; ++i) {
minimap_colors[i] = colorFromEightBit(i).GetRGB();
}
for (MapIterator mit = begin(); mit != end(); ++mit) {
if ((*mit)->get() == nullptr || (*mit)->empty()) {
continue;
}
Position pos = (*mit)->getPosition();
if (pos.x < min_x) {
min_x = pos.x;
}
if (pos.y < min_y) {
min_y = pos.y;
}
if (pos.x > max_x) {
max_x = pos.x;
}
if (pos.y > max_y) {
max_y = pos.y;
}
}
int minimap_width = max_x - min_x + 1;
int minimap_height = max_y - min_y + 1;
pic = newd uint8_t[minimap_width * minimap_height]; // 1 byte per pixel
memset(pic, 0, minimap_width * minimap_height);
int tiles_iterated = 0;
for (MapIterator mit = begin(); mit != end(); ++mit) {
Tile* tile = (*mit)->get();
++tiles_iterated;
if (tiles_iterated % 8192 == 0 && displaydialog) {
g_gui.SetLoadDone(int(tiles_iterated / double(tilecount) * 90.0));
}
if (tile->empty() || tile->getZ() != floor) {
continue;
}
// std::cout << "Pixel : " << (tile->getY() - min_y) * width + (tile->getX() - min_x) << std::endl;
uint32_t pixelpos = (tile->getY() - min_y) * minimap_width + (tile->getX() - min_x);
uint8_t &pixel = pic[pixelpos];
for (ItemVector::const_reverse_iterator item_iter = tile->items.rbegin(); item_iter != tile->items.rend(); ++item_iter) {
if ((*item_iter)->getMiniMapColor()) {
pixel = (*item_iter)->getMiniMapColor();
break;
}
}
if (pixel == 0) {
// check ground too
if (tile->hasGround()) {
pixel = tile->ground->getMiniMapColor();
}
}
}
// Create a file for writing
FileWriteHandle fh(nstr(filename.GetFullPath()));
if (!fh.isOpen()) {
delete[] pic;
return false;
}
// Store the magic number
fh.addRAW("BM");
// Store the file size
// We need to predict how large it will be
uint32_t file_size = 14 // header
+ 40 // image data header
+ 256 * 4 // color palette
+ ((minimap_width + 3) / 4 * 4) * height; // pixels
fh.addU32(file_size);
// Two values reserved, must always be 0.
fh.addU16(0);
fh.addU16(0);
// Bitmapdata offset
fh.addU32(14 + 40 + 256 * 4);
// Header size
fh.addU32(40);
// Header width/height
fh.addU32(minimap_width);
fh.addU32(minimap_height);
// Color planes
fh.addU16(1);
// bits per pixel, OT map format is 8
fh.addU16(8);
// compression type, 0 is no compression
fh.addU32(0);
// image size, 0 is valid if we use no compression
fh.addU32(0);
// horizontal/vertical resolution in pixels / meter
fh.addU32(4000);
fh.addU32(4000);
// Number of colors
fh.addU32(256);
// Important colors, 0 is all
fh.addU32(0);
// Write the color palette
for (int i = 0; i < 256; ++i) {
fh.addU32(minimap_colors[i]);
}
// Bitmap width must be divisible by four, calculate how much padding we need
int padding = ((minimap_width & 3) != 0 ? 4 - (minimap_width & 3) : 0);
// Bitmap rows are saved in reverse order
for (int y = minimap_height - 1; y >= 0; --y) {
fh.addRAW(pic + y * minimap_width, minimap_width);
for (int i = 0; i < padding; ++i) {
fh.addU8(0);
}
if (y % 100 == 0 && displaydialog) {
g_gui.SetLoadDone(90 + int((minimap_height - y) / double(minimap_height) * 10.0));
}
}
delete[] pic;
// fclose(file);
fh.close();
} catch (...) {
delete[] pic;
}
return true;
}
void Map::updateUniqueIds(Tile* old_tile, Tile* new_tile) {
if (old_tile && old_tile->hasUniqueItem()) {
if (old_tile->ground) {
uint16_t uid = old_tile->ground->getUniqueID();
if (uid != 0) {
removeUniqueId(uid);
}
}
for (const Item* item : old_tile->items) {
if (item) {
uint16_t uid = item->getUniqueID();
if (uid != 0) {
removeUniqueId(uid);
}
}
}
}
if (new_tile && new_tile->hasUniqueItem()) {
if (new_tile->ground) {
uint16_t uid = new_tile->ground->getUniqueID();
if (uid != 0) {
addUniqueId(uid);
}
}
for (const Item* item : new_tile->items) {
if (item) {
uint16_t uid = item->getUniqueID();
if (uid != 0) {
addUniqueId(uid);
}
}
}
}
}
void Map::addUniqueId(uint16_t uid) {
auto it = std::find(uniqueIds.begin(), uniqueIds.end(), uid);
if (it == uniqueIds.end()) {
uniqueIds.push_back(uid);
}
}
void Map::removeUniqueId(uint16_t uid) {
auto it = std::find(uniqueIds.begin(), uniqueIds.end(), uid);
if (it != uniqueIds.end()) {
uniqueIds.erase(it);
}
}
bool Map::hasUniqueId(uint16_t uid) const {
if (uid < rme::MinUniqueId || uniqueIds.empty()) {
return false;
}
auto it = std::find(uniqueIds.begin(), uniqueIds.end(), uid);
return it != uniqueIds.end();
}
int64_t RemoveMonstersOnMap(Map &map, bool selectedOnly) {
int64_t done = 0;
int64_t removed = 0;
MapIterator it = map.begin();
MapIterator end = map.end();
while (it != end) {
++done;
Tile* tile = (*it)->get();
if (selectedOnly && !tile->isSelected()) {
++it;
continue;
}
for (auto monster : tile->monsters) {
delete monster;
++removed;
}
tile->monsters.clear();
++it;
}
return removed;
}
int64_t EditMonsterSpawnTime(Map &map, bool selectedOnly, int32_t spawnTime) {
int64_t done = 0;
int64_t updated = 0;
MapIterator it = map.begin();
MapIterator end = map.end();
while (it != end) {
++done;
Tile* tile = (*it)->get();
if (selectedOnly && !tile->isSelected()) {
++it;
continue;
}
for (auto monster : tile->monsters) {
monster->setSpawnMonsterTime(spawnTime);
++updated;
}
++it;
}
return updated;
}
std::pair> CountMonstersOnMap(Map &map, bool selectedOnly) {
int64_t done = 0;
int64_t total = 0;
std::unordered_map monsterCount;
MapIterator it = map.begin();
MapIterator end = map.end();
while (it != end) {
++done;
Tile* tile = (*it)->get();
if (selectedOnly && !tile->isSelected()) {
++it;
continue;
}
for (const auto monster : tile->monsters) {
++total;
++monsterCount[monster->getName()];
}
++it;
}
return std::make_pair(total, monsterCount);
}