From 45acebc85dee18c0c41693fe81ed68f58fc3a0db Mon Sep 17 00:00:00 2001 From: Pedro Cruz Date: Wed, 2 Oct 2024 16:01:28 -0300 Subject: [PATCH] feat: monsters weight (#122) This adds the possibility to add stacked monsters and set weight into the stacked monsters to randomize their spawn. --- source/common_windows.cpp | 14 +-- source/copybuffer.cpp | 44 +++++-- source/editor.cpp | 28 +++-- source/gui_ids.h | 1 + source/iomap_otbm.cpp | 70 ++++++----- source/main.h | 2 + source/main_menubar.cpp | 31 +++-- source/map.cpp | 13 +- source/map_display.cpp | 208 +++++++++++++++---------------- source/map_display.h | 1 + source/map_drawer.cpp | 24 +++- source/monster.cpp | 11 +- source/monster.h | 17 ++- source/monster_brush.cpp | 19 +-- source/monsters.cpp | 4 + source/old_properties_window.cpp | 11 +- source/old_properties_window.h | 1 + source/palette_monster.cpp | 10 +- source/palette_monster.h | 1 + source/selection.cpp | 37 ++++++ source/selection.h | 2 + source/settings.cpp | 1 + source/settings.h | 1 + source/spawn_monster_brush.cpp | 2 +- source/tile.cpp | 105 +++++++++++----- source/tile.h | 9 +- 26 files changed, 428 insertions(+), 239 deletions(-) diff --git a/source/common_windows.cpp b/source/common_windows.cpp index f6494966..eb4c4f07 100644 --- a/source/common_windows.cpp +++ b/source/common_windows.cpp @@ -211,14 +211,14 @@ struct MapConversionContext { NpcMap npcType; void operator()(Map &map, Tile* tile, long long done) { - if (tile->monster) { - MonsterMap::iterator f = monsterType.find(tile->monster->getName()); - if (f == monsterType.end()) { - MonsterInfo info = { - tile->monster->getName(), - tile->monster->getLookType() + for (const auto monster : tile->monsters) { + const auto it = monsterType.find(monster->getName()); + if (it == monsterType.end()) { + MonsterInfo monsterInfo = { + monster->getName(), + monster->getLookType() }; - monsterType[tile->monster->getName()] = info; + monsterType[monster->getName()] = monsterInfo; } } if (tile->npc) { diff --git a/source/copybuffer.cpp b/source/copybuffer.cpp index e93da710..2569ac34 100644 --- a/source/copybuffer.cpp +++ b/source/copybuffer.cpp @@ -62,6 +62,7 @@ void CopyBuffer::copy(Editor &editor, int floor) { int tile_count = 0; int item_count = 0; + int monsterCount = 0; copyPos = Position(0xFFFF, 0xFFFF, floor); for (Tile* tile : editor.getSelection()) { @@ -83,9 +84,12 @@ void CopyBuffer::copy(Editor &editor, int floor) { } // Monster - if (tile->monster && tile->monster->isSelected()) { - copied_tile->monster = tile->monster->deepCopy(); - } + const auto monstersSelection = tile->getSelectedMonsters(); + std::ranges::for_each(monstersSelection, [&](const auto monster) { + ++monsterCount; + copied_tile->addMonster(monster->deepCopy()); + }); + if (tile->spawnMonster && tile->spawnMonster->isSelected()) { copied_tile->spawnMonster = tile->spawnMonster->deepCopy(); } @@ -108,9 +112,16 @@ void CopyBuffer::copy(Editor &editor, int floor) { } } - std::ostringstream ss; - ss << "Copied " << tile_count << " tile" << (tile_count > 1 ? "s" : "") << " (" << item_count << " item" << (item_count > 1 ? "s" : "") << ")"; - g_gui.SetStatusText(wxstr(ss.str())); + fmt::dynamic_format_arg_store store; + + store.push_back(tile_count); + tile_count > 1 ? store.push_back("s") : store.push_back(""); + store.push_back(item_count); + item_count > 1 ? store.push_back("s") : store.push_back(""); + store.push_back(monsterCount); + monsterCount > 1 ? store.push_back("s") : store.push_back(""); + + g_gui.SetStatusText(fmt::vformat("Copied {} tile{}, {} item{} and {} monster{}", store)); } void CopyBuffer::cut(Editor &editor, int floor) { @@ -125,6 +136,7 @@ void CopyBuffer::cut(Editor &editor, int floor) { Map &map = editor.getMap(); int tile_count = 0; int item_count = 0; + int monsterCount = 0; copyPos = Position(0xFFFF, 0xFFFF, floor); BatchAction* batch = editor.createBatch(ACTION_CUT_TILES); @@ -153,9 +165,10 @@ void CopyBuffer::cut(Editor &editor, int floor) { } // Monster - if (newtile->monster && newtile->monster->isSelected()) { - copied_tile->monster = newtile->monster; - newtile->monster = nullptr; + const auto monsterSelection = newtile->popSelectedMonsters(); + for (auto monsterIt = monsterSelection.begin(); monsterIt != monsterSelection.end(); ++monsterIt) { + ++monsterCount; + copied_tile->monsters.emplace_back(*monsterIt); } if (newtile->spawnMonster && newtile->spawnMonster->isSelected()) { @@ -226,9 +239,16 @@ void CopyBuffer::cut(Editor &editor, int floor) { editor.addBatch(batch); editor.updateActions(); - std::stringstream ss; - ss << "Cut out " << tile_count << " tile" << (tile_count > 1 ? "s" : "") << " (" << item_count << " item" << (item_count > 1 ? "s" : "") << ")"; - g_gui.SetStatusText(wxstr(ss.str())); + fmt::dynamic_format_arg_store store; + + store.push_back(tile_count); + tile_count > 1 ? store.push_back("s") : store.push_back(""); + store.push_back(item_count); + item_count > 1 ? store.push_back("s") : store.push_back(""); + store.push_back(monsterCount); + monsterCount > 1 ? store.push_back("s") : store.push_back(""); + + g_gui.SetStatusText(fmt::vformat("Cut out {} tile{}, {} item{} and {} monster{}", store)); } void CopyBuffer::paste(Editor &editor, const Position &toPosition) { diff --git a/source/editor.cpp b/source/editor.cpp index dacb0b0a..2e319fa7 100644 --- a/source/editor.cpp +++ b/source/editor.cpp @@ -1100,10 +1100,10 @@ void Editor::moveSelection(const Position &offset) { new_tile->spawnMonster = nullptr; } // Move monster - if (new_tile->monster && new_tile->monster->isSelected()) { - storage_tile->monster = new_tile->monster; - new_tile->monster = nullptr; - } + const auto monstersSelection = new_tile->popSelectedMonsters(); + std::ranges::for_each(monstersSelection, [&](const auto monster) { + storage_tile->addMonster(monster); + }); // Move npc if (new_tile->npc && new_tile->npc->isSelected()) { storage_tile->npc = new_tile->npc; @@ -1325,6 +1325,7 @@ void Editor::destroySelection() { } else { int tile_count = 0; int item_count = 0; + int monsterCount = 0; PositionList tilestoborder; BatchAction* batch = actionQueue->createBatch(ACTION_DELETE_TILES); @@ -1342,11 +1343,22 @@ void Editor::destroySelection() { // Delete the items from the tile delete *iit; } - // Monster - if (newtile->monster && newtile->monster->isSelected()) { - delete newtile->monster; - newtile->monster = nullptr; + + auto monstersSelection = newtile->popSelectedMonsters(); + std::ranges::for_each(monstersSelection, [&](auto monster) { + ++monsterCount; + delete monster; + }); + // Clear the vector to avoid being used anywhere else in this block with nullptrs + monstersSelection.clear(); + + /* + for (auto monsterIt = monstersSelection.begin(); monsterIt != monstersSelection.end(); ++monsterIt) { + ++monsterCount; + // Delete the monsters from the tile + delete *monsterIt; } + */ if (newtile->spawnMonster && newtile->spawnMonster->isSelected()) { delete newtile->spawnMonster; diff --git a/source/gui_ids.h b/source/gui_ids.h index 87f75c8c..8b9f7d59 100644 --- a/source/gui_ids.h +++ b/source/gui_ids.h @@ -138,6 +138,7 @@ enum EditorActionID { PALETTE_MONSTER_SPAWN_TIME, PALETTE_MONSTER_SPAWN_SIZE, PALETTE_MONSTER_SPAWN_DENSITY, + PALETTE_MONSTER_DEFAULT_WEIGHT, PALETTE_NPC_TILESET_CHOICE, PALETTE_NPC_LISTBOX, diff --git a/source/iomap_otbm.cpp b/source/iomap_otbm.cpp index e71ed95a..7a02b1e7 100644 --- a/source/iomap_otbm.cpp +++ b/source/iomap_otbm.cpp @@ -1058,6 +1058,11 @@ bool IOMapOTBM::loadSpawnsMonster(Map &map, pugi::xml_document &doc) { spawntime = g_settings.getInteger(Config::DEFAULT_SPAWN_MONSTER_TIME); } + auto weight = static_cast(monsterNode.attribute("weight").as_uint()); + if (weight == 0) { + weight = static_cast(g_settings.getInteger(Config::MONSTER_DEFAULT_WEIGHT)); + } + Direction direction = NORTH; int dir = monsterNode.attribute("direction").as_int(-1); if (dir >= DIRECTION_FIRST && dir <= DIRECTION_LAST) { @@ -1090,16 +1095,14 @@ bool IOMapOTBM::loadSpawnsMonster(Map &map, pugi::xml_document &doc) { } if (!monsterTile) { - wxString err; - err << "Discarding monster \"" << name << "\" at " << monsterPosition.x << ":" << monsterPosition.y << ":" << monsterPosition.z << " due to invalid position."; - warnings.Add(err); + const auto error = fmt::format("Discarding monster \"{}\" at {}:{}:{} due to invalid position", name, monsterPosition.x, monsterPosition.y, monsterPosition.z); + warnings.Add(error); break; } - if (monsterTile->monster) { - wxString err; - err << "Duplicate monster \"" << name << "\" at " << monsterPosition.x << ":" << monsterPosition.y << ":" << monsterPosition.z << " was discarded."; - warnings.Add(err); + if (monsterTile->isMonsterRepeated(name)) { + const auto error = fmt::format("Duplicate monster \"{}\" at {}:{}:{} was discarded.", name, monsterPosition.x, monsterPosition.y, monsterPosition.z); + warnings.Add(error); break; } @@ -1111,7 +1114,8 @@ bool IOMapOTBM::loadSpawnsMonster(Map &map, pugi::xml_document &doc) { Monster* monster = newd Monster(type); monster->setDirection(direction); monster->setSpawnMonsterTime(spawntime); - monsterTile->monster = monster; + monster->setWeight(weight); + monsterTile->monsters.emplace_back(monster); if (monsterTile->getLocation()->getSpawnMonsterCount() == 0) { // No monster spawn, create a newd one @@ -1762,30 +1766,36 @@ bool IOMapOTBM::saveSpawns(Map &map, pugi::xml_document &doc) { int32_t radius = spawnMonster->getSize(); spawnNode.append_attribute("radius") = radius; - for (int32_t y = -radius; y <= radius; ++y) { - for (int32_t x = -radius; x <= radius; ++x) { - Tile* monster_tile = map.getTile(spawnPosition + Position(x, y, 0)); - if (monster_tile) { - Monster* monster = monster_tile->monster; - if (monster && !monster->isSaved()) { - pugi::xml_node monsterNode = spawnNode.append_child("monster"); - monsterNode.append_attribute("name") = monster->getName().c_str(); - monsterNode.append_attribute("x") = x; - monsterNode.append_attribute("y") = y; - monsterNode.append_attribute("z") = spawnPosition.z; - auto monsterSpawnTime = monster->getSpawnMonsterTime(); - if (monsterSpawnTime > std::numeric_limits::max() || monsterSpawnTime < std::numeric_limits::min()) { - monsterSpawnTime = 60; - } + for (auto y = -radius; y <= radius; ++y) { + for (auto x = -radius; x <= radius; ++x) { + const auto monsterTile = map.getTile(spawnPosition + Position(x, y, 0)); + if (monsterTile) { + for (const auto monster : monsterTile->monsters) { + if (monster && !monster->isSaved()) { + pugi::xml_node monsterNode = spawnNode.append_child("monster"); + monsterNode.append_attribute("name") = monster->getName().c_str(); + monsterNode.append_attribute("x") = x; + monsterNode.append_attribute("y") = y; + monsterNode.append_attribute("z") = spawnPosition.z; + auto monsterSpawnTime = monster->getSpawnMonsterTime(); + if (monsterSpawnTime > std::numeric_limits::max() || monsterSpawnTime < std::numeric_limits::min()) { + monsterSpawnTime = 60; + } - monsterNode.append_attribute("spawntime") = monsterSpawnTime; - if (monster->getDirection() != NORTH) { - monsterNode.append_attribute("direction") = monster->getDirection(); - } + monsterNode.append_attribute("spawntime") = monsterSpawnTime; + if (monster->getDirection() != NORTH) { + monsterNode.append_attribute("direction") = monster->getDirection(); + } - // Mark as saved - monster->save(); - monsterList.push_back(monster); + if (monsterTile->monsters.size() > 1) { + const auto weight = monster->getWeight(); + monsterNode.append_attribute("weight") = weight > 0 ? weight : g_settings.getInteger(Config::MONSTER_DEFAULT_WEIGHT); + } + + // Mark as saved + monster->save(); + monsterList.push_back(monster); + } } } } diff --git a/source/main.h b/source/main.h index 7b985622..d7e9c136 100644 --- a/source/main.h +++ b/source/main.h @@ -47,6 +47,8 @@ _Ret_bytecap_(_Size) inline void* __CRTDECL operator new[](size_t _Size, const c #include #include +#include +#include #include #include "definitions.h" diff --git a/source/main_menubar.cpp b/source/main_menubar.cpp index afef091f..3938e52e 100644 --- a/source/main_menubar.cpp +++ b/source/main_menubar.cpp @@ -1540,7 +1540,7 @@ void MainMenuBar::OnMapRemoveEmptyMonsterSpawns(wxCommandEvent &WXUNUSED(event)) g_gui.CreateLoadBar("Searching map for empty monsters spawns to remove..."); Map &map = g_gui.GetCurrentMap(); - MonsterVector monster; + MonsterVector monsters; TileVector toDeleteSpawns; for (const auto &spawnPosition : map.spawnsMonster) { Tile* tile = map.getTile(spawnPosition); @@ -1551,13 +1551,22 @@ void MainMenuBar::OnMapRemoveEmptyMonsterSpawns(wxCommandEvent &WXUNUSED(event)) const int32_t radius = tile->spawnMonster->getSize(); bool empty = true; - for (int32_t y = -radius; y <= radius; ++y) { - for (int32_t x = -radius; x <= radius; ++x) { - Tile* creature_tile = map.getTile(spawnPosition + Position(x, y, 0)); - if (creature_tile && creature_tile->monster && !creature_tile->monster->isSaved()) { - creature_tile->monster->save(); - monster.push_back(creature_tile->monster); - empty = false; + for (auto y = -radius; y <= radius; ++y) { + for (auto x = -radius; x <= radius; ++x) { + const auto creatureTile = map.getTile(spawnPosition + Position(x, y, 0)); + if (creatureTile) { + for (const auto monster : creatureTile->monsters) { + if (empty) { + empty = false; + } + + if (monster->isSaved()) { + continue; + } + + monster->save(); + monsters.push_back(monster); + } } } } @@ -1567,7 +1576,7 @@ void MainMenuBar::OnMapRemoveEmptyMonsterSpawns(wxCommandEvent &WXUNUSED(event)) } } - for (Monster* monster : monster) { + for (const auto monster : monsters) { monster->reset(); } @@ -1847,9 +1856,7 @@ void MainMenuBar::OnMapStatistics(wxCommandEvent &WXUNUSED(event)) { spawn_npc_count += 1; } - if (tile->monster) { - monster_count += 1; - } + monster_count += tile->monsters.size(); if (tile->npc) { npc_count += 1; diff --git a/source/map.cpp b/source/map.cpp index b591fc7a..6a5fe42a 100644 --- a/source/map.cpp +++ b/source/map.cpp @@ -893,12 +893,13 @@ int64_t RemoveMonstersOnMap(Map &map, bool selectedOnly) { ++it; continue; } - if (tile->monster) { - delete tile->monster; - tile->monster = nullptr; + for (auto monster : tile->monsters) { + delete monster; ++removed; } + tile->monsters.clear(); + ++it; } return removed; @@ -919,10 +920,10 @@ std::pair> CountMonstersOnMap( ++it; continue; } - if (tile->monster) { + + for (const auto monster : tile->monsters) { ++total; - std::string monsterName = tile->monster->getName(); - ++monsterCount[monsterName]; + ++monsterCount[monster->getName()]; } ++it; diff --git a/source/map_display.cpp b/source/map_display.cpp index d4fb8652..a1002bc5 100644 --- a/source/map_display.cpp +++ b/source/map_display.cpp @@ -393,60 +393,55 @@ Position MapCanvas::GetCursorPosition() const { } void MapCanvas::UpdatePositionStatus(int x, int y) { - if (x == -1) { - x = cursor_x; - } - if (y == -1) { - y = cursor_y; - } + + x == -1 ? cursor_x : x; + y == -1 ? cursor_y : y; int map_x, map_y; ScreenToMap(x, y, &map_x, &map_y); - wxString ss; - ss << "x: " << map_x << " y:" << map_y << " z:" << floor; - g_gui.root->SetStatusText(ss, 2); + g_gui.root->SetStatusText(fmt::format("x: {} y: {} z: {}", map_x, map_y, floor), 2); - ss = ""; - Tile* tile = editor.getMap().getTile(map_x, map_y, floor); - if (tile) { - if (tile->spawnMonster && g_settings.getInteger(Config::SHOW_SPAWNS_MONSTER)) { - ss << "Monster spawn radius: " << tile->spawnMonster->getSize(); - } else if (tile->monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { - ss << ("Monster"); - ss << " \"" << wxstr(tile->monster->getName()) << "\" spawntime: " << tile->monster->getSpawnMonsterTime(); - } else if (tile->spawnNpc && g_settings.getInteger(Config::SHOW_SPAWNS_NPC)) { - ss << "Npc spawn radius: " << tile->spawnNpc->getSize(); - } else if (tile->npc && g_settings.getInteger(Config::SHOW_NPCS)) { - ss << ("NPC"); - ss << " \"" << wxstr(tile->npc->getName()) << "\" spawntime: " << tile->npc->getSpawnNpcTime(); - } else if (Item* item = tile->getTopItem()) { - ss << "Item \"" << wxstr(item->getName()) << "\""; - ss << " id:" << item->getID(); - ss << " cid:" << item->getClientID(); - if (item->getUniqueID()) { - ss << " uid:" << item->getUniqueID(); - } - if (item->getActionID()) { - ss << " aid:" << item->getActionID(); - } - if (item->hasWeight()) { - wxString s; - s.Printf("%.2f", item->getWeight()); - ss << " weight: " << s; - } - } else { - ss << "Nothing"; - } - } else { - ss << "Nothing"; - } + const auto tile = editor.getMap().getTile(map_x, map_y, floor); + + std::string description = "Nothing"; if (editor.IsLive()) { editor.GetLive().updateCursor(Position(map_x, map_y, floor)); } - g_gui.root->SetStatusText(ss, 1); + if (!tile) { + g_gui.root->SetStatusText(description, 1); + return; + } + + description.clear(); + if (tile->spawnMonster && g_settings.getInteger(Config::SHOW_SPAWNS_MONSTER)) { + description = fmt::format("Monster spawn radius: {}", tile->spawnMonster->getSize()); + } else if (!tile->monsters.empty() && g_settings.getInteger(Config::SHOW_MONSTERS)) { + std::vector texts; + for (const auto monster : tile->monsters) { + const auto monsterWeight = tile->monsters.size() > 1 ? std::to_string(monster->getWeight()) : "0"; + texts.emplace_back(fmt::format("Monster \"{}\", spawntime: {}, weight: {}", monster->getName(), monster->getSpawnMonsterTime(), monsterWeight)); + } + description = fmt::format("{}", fmt::join(texts, " - ")); + } else if (tile->spawnNpc && g_settings.getInteger(Config::SHOW_SPAWNS_NPC)) { + description = fmt::format("Npc spawn radius: {}", tile->spawnNpc->getSize()); + } else if (tile->npc && g_settings.getInteger(Config::SHOW_NPCS)) { + description = fmt::format("NPC \"{}\", spawntime: {}", tile->npc->getName(), tile->npc->getSpawnNpcTime()); + } else if (const auto item = tile->getTopItem()) { + description = fmt::format("Item \"{}\", id: {}, cid: {}", item->getName(), item->getID(), item->getClientID()); + + description = item->getUniqueID() ? fmt::format("{}, uid: {}", description, item->getUniqueID()) : description; + + description = item->getActionID() ? fmt::format("{}, aid: {}", description, item->getActionID()) : description; + + description = item->hasWeight() ? fmt::format("{}, weight: {:.2f}", description, item->getWeight()) : description; + } else { + description = "Nothing"; + } + + g_gui.root->SetStatusText(description, 1); } void MapCanvas::UpdateZoomStatus() { @@ -617,8 +612,8 @@ void MapCanvas::OnMouseLeftDoubleClick(wxMouseEvent &event) { dialog = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), new_tile, new_tile->spawnMonster); } // Show monster - else if (new_tile->monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { - dialog = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), new_tile, new_tile->monster); + else if (const auto monster = new_tile->getTopMonster(); monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { + dialog = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), new_tile, monster); } // Show npc else if (new_tile->npc && g_settings.getInteger(Config::SHOW_NPCS)) { @@ -727,6 +722,7 @@ void MapCanvas::OnMouseActionClick(wxMouseEvent &event) { } } else if (event.ControlDown()) { Tile* tile = editor.getMap().getTile(mouse_map_x, mouse_map_y, floor); + const auto monster = tile->getTopMonster(); if (tile) { // Show monster spawn if (tile->spawnMonster && g_settings.getInteger(Config::SHOW_SPAWNS_MONSTER)) { @@ -739,30 +735,16 @@ void MapCanvas::OnMouseActionClick(wxMouseEvent &event) { selection.finish(); // Finish selection session selection.updateSelectionCount(); // Show monsters - } else if (tile->monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { + } else if (monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { selection.start(); // Start selection session - if (tile->monster->isSelected()) { - selection.remove(tile, tile->monster); + if (monster->isSelected()) { + selection.remove(tile, monster); } else { - selection.add(tile, tile->monster); + selection.add(tile, monster); } - selection.finish(); // Finish selection session + selection.finish(); selection.updateSelectionCount(); - } else { - Item* item = tile->getTopItem(); - if (item) { - selection.start(); // Start selection session - if (item->isSelected()) { - selection.remove(tile, item); - } else { - selection.add(tile, item); - } - selection.finish(); // Finish selection session - selection.updateSelectionCount(); - } - } - // Show npcs - if (tile->spawnNpc && g_settings.getInteger(Config::SHOW_SPAWNS_NPC)) { + } else if (tile->spawnNpc && g_settings.getInteger(Config::SHOW_SPAWNS_NPC)) { selection.start(); // Start selection session if (tile->spawnNpc->isSelected()) { selection.remove(tile, tile->spawnNpc); @@ -819,8 +801,8 @@ void MapCanvas::OnMouseActionClick(wxMouseEvent &event) { drag_start_y = mouse_map_y; drag_start_z = floor; // Show monsters - } else if (tile->monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { - selection.add(tile, tile->monster); + } else if (const auto monster = tile->getTopMonster(); monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { + selection.add(tile, monster); dragging = true; drag_start_x = mouse_map_x; drag_start_y = mouse_map_y; @@ -1192,10 +1174,10 @@ void MapCanvas::OnMouseActionRelease(wxMouseEvent &event) { selection.finish(); // Finish the selection session selection.updateSelectionCount(); } - } else if (tile->monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { - if (!tile->monster->isSelected()) { + } else if (const auto monster = tile->getTopMonster(); monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { + if (!monster->isSelected()) { selection.start(); // Start a selection session - selection.add(tile, tile->monster); + selection.add(tile, monster); selection.finish(); // Finish the selection session selection.updateSelectionCount(); } @@ -1209,7 +1191,7 @@ void MapCanvas::OnMouseActionRelease(wxMouseEvent &event) { } else if (tile->npc && g_settings.getInteger(Config::SHOW_NPCS)) { if (!tile->npc->isSelected()) { selection.start(); // Start a selection session - selection.add(tile, tile->monster); + selection.add(tile, tile->npc); selection.finish(); // Finish the selection session selection.updateSelectionCount(); } @@ -1443,8 +1425,8 @@ void MapCanvas::OnMousePropertiesClick(wxMouseEvent &event) { selection.commit(); if (tile->spawnMonster && g_settings.getInteger(Config::SHOW_SPAWNS_MONSTER)) { selection.add(tile, tile->spawnMonster); - } else if (tile->monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { - selection.add(tile, tile->monster); + } else if (const auto monster = tile->getTopMonster(); monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { + selection.add(tile, monster); } else if (tile->npc && g_settings.getInteger(Config::SHOW_NPCS)) { selection.add(tile, tile->npc); } else if (tile->spawnNpc && g_settings.getInteger(Config::SHOW_SPAWNS_NPC)) { @@ -2305,8 +2287,8 @@ void MapCanvas::OnSelectMonsterBrush(wxCommandEvent &WXUNUSED(event)) { return; } - if (tile->monster) { - g_gui.SelectBrush(tile->monster->getBrush(), TILESET_MONSTER); + if (const auto monster = tile->getTopMonster(); monster) { + g_gui.SelectBrush(monster->getBrush(), TILESET_MONSTER); } } @@ -2379,54 +2361,58 @@ void MapCanvas::OnProperties(wxCommandEvent &WXUNUSED(event)) { return; } - Tile* tile = editor.getSelection().getSelectedTile(); + const auto tile = editor.getSelection().getSelectedTile(); if (!tile) { return; } ASSERT(tile->isSelected()); - Tile* new_tile = tile->deepCopy(editor.getMap()); + const auto newTile = tile->deepCopy(editor.getMap()); wxDialog* w = nullptr; - if (new_tile->spawnMonster && g_settings.getInteger(Config::SHOW_SPAWNS_MONSTER)) { - w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), new_tile, new_tile->spawnMonster); - } else if (new_tile->monster && g_settings.getInteger(Config::SHOW_MONSTERS)) { - w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), new_tile, new_tile->monster); - } else if (new_tile->npc && g_settings.getInteger(Config::SHOW_NPCS)) { - w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), new_tile, new_tile->npc); - } else if (new_tile->spawnNpc && g_settings.getInteger(Config::SHOW_SPAWNS_NPC)) { - w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), new_tile, new_tile->spawnNpc); + if (newTile->spawnMonster && g_settings.getInteger(Config::SHOW_SPAWNS_MONSTER)) { + w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), newTile, newTile->spawnMonster); + } else if (g_settings.getInteger(Config::SHOW_MONSTERS)) { + std::vector selectedMonsters = newTile->getSelectedMonsters(); + + const auto it = std::ranges::find_if(selectedMonsters | std::views::reverse, [&](const auto itMonster) { + return itMonster->isSelected(); + }); + + if (it == selectedMonsters.rend()) { + return; + } + + w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), newTile, *it); + } else if (newTile->npc && g_settings.getInteger(Config::SHOW_NPCS)) { + w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), newTile, newTile->npc); + } else if (newTile->spawnNpc && g_settings.getInteger(Config::SHOW_SPAWNS_NPC)) { + w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), newTile, newTile->spawnNpc); } else { - ItemVector selected_items = new_tile->getSelectedItems(); - - Item* item = nullptr; - int count = 0; - for (ItemVector::iterator it = selected_items.begin(); it != selected_items.end(); ++it) { - ++count; - if ((*it)->isSelected()) { - item = *it; - } + const auto selectedItems = newTile->getSelectedItems(); + + const auto it = std::ranges::find_if(selectedItems | std::views::reverse, [&](const auto itItem) { + return itItem->isSelected(); + }); + + if (it == selectedItems.rend()) { + return; } - if (item) { - if (editor.getMap().getVersion().otbm >= MAP_OTBM_4) { - w = newd PropertiesWindow(g_gui.root, &editor.getMap(), new_tile, item); - } else { - w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), new_tile, item); - } + if (editor.getMap().getVersion().otbm >= MAP_OTBM_4) { + w = newd PropertiesWindow(g_gui.root, &editor.getMap(), newTile, *it); } else { - return; + w = newd OldPropertiesWindow(g_gui.root, &editor.getMap(), newTile, *it); } } - int ret = w->ShowModal(); - if (ret != 0) { - Action* action = editor.createAction(ACTION_CHANGE_PROPERTIES); - action->addChange(newd Change(new_tile)); + if (w->ShowModal() != 0) { + const auto action = editor.createAction(ACTION_CHANGE_PROPERTIES); + action->addChange(newd Change(newTile)); editor.addAction(action); } else { // Cancel! - delete new_tile; + delete newTile; } w->Destroy(); } @@ -2539,13 +2525,15 @@ void MapPopupMenu::Update() { if (editor.getSelection().size() == 1) { Tile* tile = editor.getSelection().getSelectedTile(); ItemVector selected_items = tile->getSelectedItems(); + std::vector selectedMonsters = tile->getSelectedMonsters(); bool hasWall = false; bool hasCarpet = false; bool hasTable = false; Item* topItem = nullptr; Item* topSelectedItem = (selected_items.size() == 1 ? selected_items.back() : nullptr); - Monster* topMonster = tile->monster; + Monster* topMonster = nullptr; + Monster* topSelectedMonster = (selectedMonsters.size() == 1 ? selectedMonsters.back() : nullptr); SpawnMonster* topSpawnMonster = tile->spawnMonster; Npc* topNpc = tile->npc; SpawnNpc* topSpawnNpc = tile->spawnNpc; @@ -2586,7 +2574,7 @@ void MapPopupMenu::Update() { AppendSeparator(); } - if (topSelectedItem || topMonster || topNpc || topItem) { + if (topSelectedItem || topMonster || topSelectedMonster || topNpc || topItem) { Teleport* teleport = dynamic_cast(topSelectedItem); if (topSelectedItem && (topSelectedItem->isBrushDoor() || topSelectedItem->isRoteable() || teleport)) { @@ -2613,7 +2601,7 @@ void MapPopupMenu::Update() { } } - if (topMonster) { + if (topMonster || topSelectedMonster) { Append(MAP_POPUP_MENU_SELECT_MONSTER_BRUSH, "Select Monster", "Uses the current monster as a monster brush"); } diff --git a/source/map_display.h b/source/map_display.h index 9158e463..fb494ffc 100644 --- a/source/map_display.h +++ b/source/map_display.h @@ -143,6 +143,7 @@ class MapCanvas : public wxGLCanvas { static bool processed[BLOCK_SIZE * BLOCK_SIZE]; + Tile* lastTile; Editor &editor; MapDrawer* drawer; int keyCode; diff --git a/source/map_drawer.cpp b/source/map_drawer.cpp index 9cf6806f..d65a36c9 100644 --- a/source/map_drawer.cpp +++ b/source/map_drawer.cpp @@ -474,9 +474,12 @@ void MapDrawer::DrawSecondaryMap(int map_z) { } // Monsters - if (!hidden && options.show_monsters && tile->monster) { - BlitCreature(draw_x, draw_y, tile->monster); + if (!hidden && options.show_monsters && !tile->monsters.empty()) { + for (auto monster : tile->monsters) { + BlitCreature(draw_x, draw_y, monster); + } } + // NPCS if (!hidden && options.show_npcs && tile->npc) { BlitCreature(draw_x, draw_y, tile->npc); @@ -609,9 +612,16 @@ void MapDrawer::DrawDraggingShadow() { } } - if (options.show_monsters && tile->monster && tile->monster->isSelected()) { - BlitCreature(draw_x, draw_y, tile->monster); + if (options.show_monsters && !tile->monsters.empty()) { + for (auto monster : tile->monsters) { + if (!monster->isSelected()) { + continue; + } + + BlitCreature(draw_x, draw_y, monster); + } } + if (tile->spawnMonster && tile->spawnMonster->isSelected()) { DrawIndicator(draw_x, draw_y, EDITOR_SPRITE_MONSTERS, 160, 160, 160, 160); } @@ -1613,8 +1623,10 @@ void MapDrawer::DrawTile(TileLocation* location) { } } - if (!hidden && options.show_monsters && tile->monster) { - BlitCreature(draw_x, draw_y, tile->monster); + if (!hidden && options.show_monsters && !tile->monsters.empty()) { + for (auto monster : tile->monsters) { + BlitCreature(draw_x, draw_y, monster); + } } if (!hidden && options.show_npcs && tile->npc) { diff --git a/source/monster.cpp b/source/monster.cpp index 5cc17c59..4680dc45 100644 --- a/source/monster.cpp +++ b/source/monster.cpp @@ -19,28 +19,31 @@ #include "monster.h" -Monster::Monster(MonsterType* type) : +Monster::Monster(MonsterType* type, uint8_t weight) : direction(NORTH), spawntime(0), saved(false), - selected(false) { + selected(false), + weight(weight) { if (type) { type_name = type->name; } } -Monster::Monster(const std::string &type_name) : +Monster::Monster(const std::string &type_name, uint8_t weight) : type_name(type_name), direction(NORTH), spawntime(0), saved(false), - selected(false) { + selected(false), + weight(weight) { //// } Monster* Monster::deepCopy() const { Monster* copy = newd Monster(type_name); copy->spawntime = spawntime; + copy->weight = weight; copy->direction = direction; copy->selected = selected; copy->saved = saved; diff --git a/source/monster.h b/source/monster.h index 43691cc5..4325d2a6 100644 --- a/source/monster.h +++ b/source/monster.h @@ -23,8 +23,8 @@ class Monster { public: - Monster(MonsterType* type); - Monster(const std::string &type_name); + Monster(MonsterType* type, uint8_t weight = 0); + Monster(const std::string &type_name, uint8_t weight = 0); Monster* deepCopy() const; @@ -50,9 +50,21 @@ class Monster { selected = true; } + [[nodiscard]] const std::string &getTypeName() const noexcept { + return type_name; + } + std::string getName() const; MonsterBrush* getBrush() const; + [[nodiscard]] int getWeight() const noexcept { + return weight; + } + + void setWeight(int newWeight) noexcept { + weight = newWeight; + } + int getSpawnMonsterTime() const noexcept { return spawntime; } @@ -74,6 +86,7 @@ class Monster { protected: std::string type_name; Direction direction; + uint8_t weight; int spawntime; bool saved; bool selected; diff --git a/source/monster_brush.cpp b/source/monster_brush.cpp index 49db3f5e..0a3e5ac5 100644 --- a/source/monster_brush.cpp +++ b/source/monster_brush.cpp @@ -64,18 +64,22 @@ bool MonsterBrush::canDraw(BaseMap* map, const Position &position) const { } void MonsterBrush::undraw(BaseMap* map, Tile* tile) { - delete tile->monster; - tile->monster = nullptr; + // It does nothing } void MonsterBrush::drawMonster(BaseMap* map, Tile* tile, void* parameter) { ASSERT(tile); ASSERT(parameter); - if (canDraw(map, tile->getPosition())) { - undraw(map, tile); + if (tile && canDraw(map, tile->getPosition())) { if (monster_type) { - tile->monster = newd Monster(monster_type); - tile->monster->setSpawnMonsterTime(*(int*)parameter); + const auto it = std::ranges::find_if(tile->monsters, [&](const auto monster) { + return strcmp(monster->getTypeName().c_str(), monster_type->name.c_str()) == 0; + }); + if (it == tile->monsters.end()) { + const auto monster = newd Monster(monster_type); + monster->setSpawnMonsterTime(*(int*)parameter); + tile->monsters.emplace_back(monster); + } } } } @@ -83,8 +87,7 @@ void MonsterBrush::drawMonster(BaseMap* map, Tile* tile, void* parameter) { void MonsterBrush::draw(BaseMap* map, Tile* tile, void* parameter) { ASSERT(tile); ASSERT(parameter); - if (canDraw(map, tile->getPosition())) { - undraw(map, tile); + if (tile && canDraw(map, tile->getPosition())) { if (monster_type) { if (tile->spawnMonster == nullptr && tile->getLocation()->getSpawnMonsterCount() == 0) { // manually place spawnMonster on location diff --git a/source/monsters.cpp b/source/monsters.cpp index 681d8c89..238af01e 100644 --- a/source/monsters.cpp +++ b/source/monsters.cpp @@ -178,6 +178,10 @@ void MonsterDatabase::clear() { } MonsterType* MonsterDatabase::operator[](const std::string &name) { + if (name.empty()) { + return nullptr; + } + MonsterMap::iterator iter = monster_map.find(as_lower_str(name)); if (iter != monster_map.end()) { return iter->second; diff --git a/source/old_properties_window.cpp b/source/old_properties_window.cpp index 80bf0f68..a0f3abc9 100644 --- a/source/old_properties_window.cpp +++ b/source/old_properties_window.cpp @@ -389,6 +389,10 @@ OldPropertiesWindow::OldPropertiesWindow(wxWindow* win_parent, const Map* map, c // count_field->SetSelection(-1, -1); subsizer->Add(count_field, wxSizerFlags(1).Expand()); + subsizer->Add(newd wxStaticText(this, wxID_ANY, "Weight")); + monsterWeightField = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_monster->getWeight()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 100); + subsizer->Add(monsterWeightField, wxSizerFlags(1).Expand()); + subsizer->Add(newd wxStaticText(this, wxID_ANY, "Direction")); direction_field = newd wxChoice(this, wxID_ANY); @@ -699,13 +703,18 @@ void OldPropertiesWindow::OnClickOK(wxCommandEvent &WXUNUSED(event)) { edit_item->setActionID(new_aid); } } else if (edit_monster) { - int new_spawn_monster_time = count_field->GetValue(); + const auto new_spawn_monster_time = count_field->GetValue(); + const auto newWeight = static_cast(monsterWeightField->GetValue()); edit_monster->setSpawnMonsterTime(new_spawn_monster_time); int* new_dir = reinterpret_cast(direction_field->GetClientData( direction_field->GetSelection() )); + if (newWeight) { + edit_monster->setWeight(newWeight); + } + if (new_dir) { edit_monster->setDirection((Direction)*new_dir); } diff --git a/source/old_properties_window.h b/source/old_properties_window.h index 7f41b955..493c1050 100644 --- a/source/old_properties_window.h +++ b/source/old_properties_window.h @@ -44,6 +44,7 @@ class OldPropertiesWindow : public ObjectPropertiesWindowBase { protected: wxSpinCtrl* count_field; wxChoice* direction_field; + wxSpinCtrl* monsterWeightField = nullptr; wxSpinCtrl* action_id_field; wxSpinCtrl* unique_id_field; wxSpinCtrl* door_id_field; diff --git a/source/palette_monster.cpp b/source/palette_monster.cpp index 70cbde12..8c6e4a62 100644 --- a/source/palette_monster.cpp +++ b/source/palette_monster.cpp @@ -72,7 +72,7 @@ MonsterPalettePanel::MonsterPalettePanel(wxWindow* parent, wxWindowID id) : // sidesizer->Add(180, 1, wxEXPAND); - wxFlexGridSizer* grid = newd wxFlexGridSizer(3, 10, 10); + wxFlexGridSizer* grid = newd wxFlexGridSizer(4, 3, 10, 10); grid->AddGrowableCol(1); grid->Add(newd wxStaticText(this, wxID_ANY, "Spawntime")); @@ -86,9 +86,15 @@ MonsterPalettePanel::MonsterPalettePanel(wxWindow* parent, wxWindowID id) : grid->Add(spawn_monster_size_spin, 0, wxEXPAND); spawn_monster_brush_button = newd wxToggleButton(this, PALETTE_SPAWN_MONSTER_BRUSH_BUTTON, "Place Spawn"); grid->Add(spawn_monster_brush_button, 0, wxEXPAND); + grid->Add(newd wxStaticText(this, wxID_ANY, "Spawn density %")); monster_spawndensity_spin = newd wxSpinCtrl(this, PALETTE_MONSTER_SPAWN_DENSITY, i2ws(g_settings.getInteger(Config::SPAWN_MONSTER_DENSITY)), wxDefaultPosition, wxSize(50, 20), wxSP_ARROW_KEYS, 0, 3600, g_settings.getInteger(Config::SPAWN_MONSTER_DENSITY)); - grid->Add(monster_spawndensity_spin, 0, wxEXPAND); + grid->Add(monster_spawndensity_spin); + + grid->AddSpacer(1); + grid->Add(newd wxStaticText(this, wxID_ANY, "Default weight %")); + monsterDefaultWeight = newd wxSpinCtrl(this, PALETTE_MONSTER_DEFAULT_WEIGHT, i2ws(g_settings.getInteger(Config::MONSTER_DEFAULT_WEIGHT)), wxDefaultPosition, wxSize(50, 20), wxSP_ARROW_KEYS, 0, 100, g_settings.getInteger(Config::MONSTER_DEFAULT_WEIGHT)); + grid->Add(monsterDefaultWeight); sidesizer->Add(grid, 0, wxEXPAND); topsizer->Add(sidesizer, 0, wxEXPAND); diff --git a/source/palette_monster.h b/source/palette_monster.h index 29450d77..3cd1ca66 100644 --- a/source/palette_monster.h +++ b/source/palette_monster.h @@ -74,6 +74,7 @@ class MonsterPalettePanel : public PalettePanel { wxSpinCtrl* monster_spawntime_spin; wxSpinCtrl* spawn_monster_size_spin; wxSpinCtrl* monster_spawndensity_spin; + wxSpinCtrl* monsterDefaultWeight = nullptr; bool handling_event; diff --git a/source/selection.cpp b/source/selection.cpp index 0ad18993..242fba28 100644 --- a/source/selection.cpp +++ b/source/selection.cpp @@ -137,6 +137,23 @@ void Selection::add(const Tile* tile, SpawnNpc* spawnNpc) { subsession->addChange(newd Change(new_tile)); } +void Selection::add(const Tile* tile, const std::vector &monsters) { + ASSERT(subsession); + ASSERT(tile); + ASSERT(monster); + + // Make a copy of the tile with the monsters selected + for (const auto monster : monsters) { + monster->select(); + } + Tile* new_tile = tile->deepCopy(editor.getMap()); + for (const auto monster : monsters) { + monster->deselect(); + } + + subsession->addChange(newd Change(new_tile)); +} + void Selection::add(const Tile* tile, Monster* monster) { ASSERT(subsession); ASSERT(tile); @@ -229,6 +246,26 @@ void Selection::remove(Tile* tile, SpawnNpc* spawnNpc) { subsession->addChange(newd Change(new_tile)); } +void Selection::remove(Tile* tile, const std::vector &monsters) { + ASSERT(subsession); + ASSERT(tile); + ASSERT(monster); + + std::vector selectedMonsters; + for (const auto monster : monsters) { + if (monster->isSelected()) { + selectedMonsters.emplace_back(monster); + } + monster->deselect(); + } + Tile* new_tile = tile->deepCopy(editor.getMap()); + for (const auto monster : selectedMonsters) { + monster->select(); + } + + subsession->addChange(newd Change(new_tile)); +} + void Selection::remove(Tile* tile, Monster* monster) { ASSERT(subsession); ASSERT(tile); diff --git a/source/selection.h b/source/selection.h index 1f15d926..44aa54be 100644 --- a/source/selection.h +++ b/source/selection.h @@ -38,12 +38,14 @@ class Selection { void add(const Tile* tile, Item* item); void add(const Tile* tile, SpawnMonster* spawnMonster); void add(const Tile* tile, SpawnNpc* spawnNpc); + void add(const Tile* tile, const std::vector &monsters); void add(const Tile* tile, Monster* monster); void add(const Tile* tile, Npc* npc); void add(const Tile* tile); void remove(Tile* tile, Item* item); void remove(Tile* tile, SpawnMonster* spawnMonster); void remove(Tile* tile, SpawnNpc* spawnNpc); + void remove(Tile* tile, const std::vector &monsters); void remove(Tile* tile, Monster* monster); void remove(Tile* tile, Npc* npc); void remove(Tile* tile); diff --git a/source/settings.cpp b/source/settings.cpp index 4e6659e8..2f648d40 100644 --- a/source/settings.cpp +++ b/source/settings.cpp @@ -258,6 +258,7 @@ void Settings::IO(IOMode mode) { Int(AUTO_CREATE_SPAWN_MONSTER, 1); Int(DEFAULT_SPAWN_MONSTER_TIME, 60); Int(SPAWN_MONSTER_DENSITY, 10); + Int(MONSTER_DEFAULT_WEIGHT, 25); Int(MAX_SPAWN_MONSTER_RADIUS, 30); Int(CURRENT_SPAWN_MONSTER_RADIUS, 1); Int(AUTO_CREATE_SPAWN_NPC, 1); diff --git a/source/settings.h b/source/settings.h index 8722a95f..1b676b79 100644 --- a/source/settings.h +++ b/source/settings.h @@ -134,6 +134,7 @@ namespace Config { AUTO_CREATE_SPAWN_MONSTER, DEFAULT_SPAWN_MONSTER_TIME, SPAWN_MONSTER_DENSITY, + MONSTER_DEFAULT_WEIGHT, MAX_SPAWN_NPC_RADIUS, CURRENT_SPAWN_NPC_RADIUS, diff --git a/source/spawn_monster_brush.cpp b/source/spawn_monster_brush.cpp index bb76f453..639097e7 100644 --- a/source/spawn_monster_brush.cpp +++ b/source/spawn_monster_brush.cpp @@ -64,7 +64,7 @@ void SpawnMonsterBrush::draw(BaseMap* map, Tile* tile, void* parameter) { auto side = size * 2 + 1; int time = g_settings.getInteger(Config::DEFAULT_SPAWN_MONSTER_TIME); int density = g_settings.getInteger(Config::SPAWN_MONSTER_DENSITY); - if (tile->spawnMonster == nullptr) { + if (tile && tile->spawnMonster == nullptr) { tile->spawnMonster = newd SpawnMonster(size); auto toSpawn = (int)std::ceil((side * side) * (density / 100.0)); std::set positions; diff --git a/source/tile.cpp b/source/tile.cpp index 3bcb05b9..426fc04c 100644 --- a/source/tile.cpp +++ b/source/tile.cpp @@ -34,7 +34,6 @@ Tile::Tile(int x, int y, int z) : location(nullptr), ground(nullptr), - monster(nullptr), spawnMonster(nullptr), npc(nullptr), spawnNpc(nullptr), @@ -48,7 +47,6 @@ Tile::Tile(int x, int y, int z) : Tile::Tile(TileLocation &loc) : location(&loc), ground(nullptr), - monster(nullptr), spawnMonster(nullptr), npc(nullptr), spawnNpc(nullptr), @@ -64,7 +62,11 @@ Tile::~Tile() { delete items.back(); items.pop_back(); } - delete monster; + + while (!monsters.empty()) { + delete monsters.back(); + monsters.pop_back(); + } // printf("%d,%d,%d,%p\n", tilePos.x, tilePos.y, tilePos.z, ground); delete ground; delete spawnMonster; @@ -82,9 +84,6 @@ Tile* Tile::deepCopy(BaseMap &map) const { if (spawnNpc) { copy->spawnNpc = spawnNpc->deepCopy(); } - if (monster) { - copy->monster = monster->deepCopy(); - } if (npc) { copy->npc = npc->deepCopy(); } @@ -93,6 +92,9 @@ Tile* Tile::deepCopy(BaseMap &map) const { copy->ground = ground->deepCopy(); } + for (const auto monster : monsters) { + copy->monsters.emplace_back(monster->deepCopy()); + } for (const Item* item : items) { copy->items.push_back(item->deepCopy()); } @@ -123,9 +125,7 @@ int Tile::size() const { ++sz; } sz += items.size(); - if (monster) { - ++sz; - } + sz += monsters.size(); if (spawnMonster) { ++sz; } @@ -171,12 +171,6 @@ void Tile::merge(Tile* other) { other->ground = nullptr; } - if (other->monster) { - delete monster; - monster = other->monster; - other->monster = nullptr; - } - if (other->spawnMonster) { delete spawnMonster; spawnMonster = other->spawnMonster; @@ -195,18 +189,17 @@ void Tile::merge(Tile* other) { other->spawnNpc = nullptr; } - if (other->monster) { - delete monster; - monster = other->monster; - other->monster = nullptr; - } - if (other->npc) { delete npc; npc = other->npc; other->npc = nullptr; } + for (const auto monster : other->monsters) { + addMonster(monster); + } + other->monsters.clear(); + for (Item* item : other->items) { addItem(item); } @@ -287,6 +280,18 @@ Item* Tile::getItemAt(int index) const { return nullptr; } +void Tile::addMonster(Monster* monster) { + if (!monster) { + return; + } + + monsters.emplace_back(monster); + + if (monster->isSelected()) { + statflags |= TILESTATE_SELECTED; + } +} + void Tile::addItem(Item* item) { if (!item) { return; @@ -346,13 +351,13 @@ void Tile::select() { if (spawnNpc) { spawnNpc->select(); } - if (monster) { - monster->select(); - } if (npc) { npc->select(); } + for (const auto monster : monsters) { + monster->select(); + } for (Item* item : items) { item->select(); } @@ -370,13 +375,14 @@ void Tile::deselect() { if (spawnNpc) { spawnNpc->deselect(); } - if (monster) { - monster->deselect(); - } if (npc) { npc->deselect(); } + for (const auto monster : monsters) { + monster->deselect(); + } + for (Item* item : items) { item->deselect(); } @@ -384,6 +390,42 @@ void Tile::deselect() { statflags &= ~TILESTATE_SELECTED; } +Monster* Tile::getTopMonster() const { + return !monsters.empty() ? monsters.back() : nullptr; +} + +std::vector Tile::popSelectedMonsters() { + std::vector popMonsters; + + std::erase_if(monsters, [&](const auto monster) { + if (monster->isSelected()) { + popMonsters.emplace_back(monster); + return true; + } + + return false; + }); + + statflags &= ~TILESTATE_SELECTED; + return popMonsters; +} + +std::vector Tile::getSelectedMonsters() { + std::vector selectedMonters; + std::copy_if(monsters.begin(), monsters.end(), std::back_inserter(selectedMonters), [](const auto monster) { + return monster->isSelected(); + }); + + return selectedMonters; +} + +bool Tile::isMonsterRepeated(const std::string &searchMonster) const { + return std::ranges::find_if(monsters, [&](const auto monster) { + return monster->getTypeName() == searchMonster; + }) + != monsters.end(); +} + Item* Tile::getTopSelectedItem() { for (auto it = items.rbegin(); it != items.rend(); ++it) { if ((*it)->isSelected() && !(*it)->isMetaItem()) { @@ -471,8 +513,13 @@ void Tile::update() { if (spawnNpc && spawnNpc->isSelected()) { statflags |= TILESTATE_SELECTED; } - if (monster && monster->isSelected()) { - statflags |= TILESTATE_SELECTED; + if (!monsters.empty()) { + for (const auto monster : monsters) { + if (monster->isSelected()) { + statflags |= TILESTATE_SELECTED; + break; + } + } } if (npc && npc->isSelected()) { statflags |= TILESTATE_SELECTED; diff --git a/source/tile.h b/source/tile.h index 10ba4ca9..40b5c796 100644 --- a/source/tile.h +++ b/source/tile.h @@ -52,7 +52,7 @@ class Tile { TileLocation* location; Item* ground; ItemVector items; - Monster* monster; + std::vector monsters; SpawnMonster* spawnMonster; Npc* npc; SpawnNpc* spawnNpc; @@ -159,6 +159,13 @@ class Tile { void select(); void deselect(); + + void addMonster(Monster* monster); + Monster* getTopMonster() const; // Returns the topmost monster, or nullptr + std::vector popSelectedMonsters(); + std::vector getSelectedMonsters(); + bool isMonsterRepeated(const std::string &searchMonster) const; + // This selects borders too void selectGround(); void deselectGround();