diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index da90b03f65a..eac70f8ba74 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -1769,6 +1769,10 @@ bool ConditionDamage::getNextDamage(int32_t &damage) { } bool ConditionDamage::doDamage(const std::shared_ptr &creature, int32_t healthChange) const { + if (owner == 0) { + return false; + } + const auto &attacker = g_game().getPlayerByGUID(owner) ? g_game().getPlayerByGUID(owner)->getCreature() : g_game().getCreatureByID(owner); bool isPlayer = attacker && attacker->getPlayer(); if (creature->isSuppress(getType(), isPlayer)) { diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 13b30321a21..845544d61e9 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -35,6 +35,7 @@ std::shared_ptr Monster::createMonster(const std::string &name) { } Monster::Monster(const std::shared_ptr &mType) : + m_lowerName(asLowerCaseString(mType->name)), nameDescription(asLowerCaseString(mType->nameDescription)), mType(mType) { defaultOutfit = mType->info.outfit; diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index 6e44d8f36bf..3661eea9bef 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -49,6 +49,10 @@ class Monster final : public Creature { void setNameDescription(std::string_view nameDescription); std::string getDescription(int32_t) override; + const std::string &getLowerName() const { + return m_lowerName; + } + CreatureType_t getType() const override; const Position &getMasterPos() const; @@ -244,6 +248,7 @@ class Monster final : public Creature { ForgeClassifications_t monsterForgeClassification = ForgeClassifications_t::FORGE_NORMAL_MONSTER; std::string name; + std::string m_lowerName; std::string nameDescription; std::shared_ptr mType; diff --git a/src/creatures/npcs/npc.cpp b/src/creatures/npcs/npc.cpp index 5ce48cbbb7b..65bec559872 100644 --- a/src/creatures/npcs/npc.cpp +++ b/src/creatures/npcs/npc.cpp @@ -97,6 +97,10 @@ void Npc::setName(std::string newName) const { npcType->name = std::move(newName); } +const std::string &Npc::getLowerName() const { + return npcType->m_lowerName; +} + CreatureType_t Npc::getType() const { return CREATURETYPE_NPC; } diff --git a/src/creatures/npcs/npc.hpp b/src/creatures/npcs/npc.hpp index dc3e16961f1..47945b79e99 100644 --- a/src/creatures/npcs/npc.hpp +++ b/src/creatures/npcs/npc.hpp @@ -51,6 +51,8 @@ class Npc final : public Creature { void setName(std::string newName) const; + const std::string &getLowerName() const; + CreatureType_t getType() const override; const Position &getMasterPos() const; diff --git a/src/creatures/npcs/npcs.cpp b/src/creatures/npcs/npcs.cpp index 53ed757336f..9abc6be6efd 100644 --- a/src/creatures/npcs/npcs.cpp +++ b/src/creatures/npcs/npcs.cpp @@ -16,6 +16,9 @@ #include "lua/scripts/scripts.hpp" #include "lib/di/container.hpp" +NpcType::NpcType(const std::string &initName) : + name(initName), m_lowerName(asLowerCaseString(initName)), typeName(initName), nameDescription(initName) {}; + bool NpcType::canSpawn(const Position &pos) const { bool canSpawn = true; const bool isDay = g_game().gameIsDay(); diff --git a/src/creatures/npcs/npcs.hpp b/src/creatures/npcs/npcs.hpp index b91e1547c07..d587a17b9fa 100644 --- a/src/creatures/npcs/npcs.hpp +++ b/src/creatures/npcs/npcs.hpp @@ -77,14 +77,14 @@ class NpcType final : public SharedObject { public: NpcType() = default; - explicit NpcType(const std::string &initName) : - name(initName), typeName(initName), nameDescription(initName) {}; + explicit NpcType(const std::string &initName); // non-copyable NpcType(const NpcType &) = delete; NpcType &operator=(const NpcType &) = delete; std::string name; + std::string m_lowerName; std::string typeName; std::string nameDescription; NpcInfo info; diff --git a/src/game/game.cpp b/src/game/game.cpp index 7da1d87fb4a..54a666337d3 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -424,7 +424,7 @@ Game &Game::getInstance() { } void Game::resetMonsters() const { - for (const auto &[monsterId, monster] : getMonsters()) { + for (const auto &monster : getMonsters()) { monster->clearTargetList(); monster->clearFriendList(); } @@ -432,7 +432,7 @@ void Game::resetMonsters() const { void Game::resetNpcs() const { // Close shop window from all npcs and reset the shopPlayerSet - for (const auto &[npcId, npc] : getNpcs()) { + for (const auto &npc : getNpcs()) { npc->closeAllShopWindows(); npc->resetPlayerInteractions(); } @@ -954,11 +954,16 @@ std::shared_ptr Game::getMonsterByID(uint32_t id) { return nullptr; } - auto it = monsters.find(id); - if (it == monsters.end()) { + auto it = monstersIdIndex.find(id); + if (it == monstersIdIndex.end()) { return nullptr; } - return it->second; + + if (it->second >= monsters.size()) { + return nullptr; + } + + return monsters[it->second]; } std::shared_ptr Game::getNpcByID(uint32_t id) { @@ -966,11 +971,12 @@ std::shared_ptr Game::getNpcByID(uint32_t id) { return nullptr; } - auto it = npcs.find(id); - if (it == npcs.end()) { + auto it = npcsIdIndex.find(id); + if (it == npcsIdIndex.end()) { return nullptr; } - return it->second; + + return npcs[it->second]; } std::shared_ptr Game::getPlayerByID(uint32_t id, bool allowOffline /* = false */) { @@ -990,43 +996,41 @@ std::shared_ptr Game::getPlayerByID(uint32_t id, bool allowOffline /* = return tmpPlayer; } -std::shared_ptr Game::getCreatureByName(const std::string &s) { - if (s.empty()) { +std::shared_ptr Game::getCreatureByName(const std::string &creatureName) { + if (creatureName.empty()) { return nullptr; } - const std::string &lowerCaseName = asLowerCaseString(s); + const std::string &lowerCaseName = asLowerCaseString(creatureName); auto m_it = mappedPlayerNames.find(lowerCaseName); if (m_it != mappedPlayerNames.end()) { return m_it->second.lock(); } - for (const auto &it : npcs) { - if (lowerCaseName == asLowerCaseString(it.second->getName())) { - return it.second; - } + auto npcIterator = npcsNameIndex.find(lowerCaseName); + if (npcIterator != npcsNameIndex.end()) { + return npcs[npcIterator->second]; } - for (const auto &it : monsters) { - if (lowerCaseName == asLowerCaseString(it.second->getName())) { - return it.second; - } + auto monsterIterator = monstersNameIndex.find(lowerCaseName); + if (monsterIterator != monstersNameIndex.end()) { + return monsters[monsterIterator->second]; } return nullptr; } -std::shared_ptr Game::getNpcByName(const std::string &s) { - if (s.empty()) { +std::shared_ptr Game::getNpcByName(const std::string &npcName) { + if (npcName.empty()) { return nullptr; } - const char* npcName = s.c_str(); - for (const auto &it : npcs) { - if (strcasecmp(npcName, it.second->getName().c_str()) == 0) { - return it.second; - } + const std::string lowerCaseName = asLowerCaseString(npcName); + auto it = npcsNameIndex.find(lowerCaseName); + if (it != npcsNameIndex.end()) { + return npcs[it->second]; } + return nullptr; } @@ -3184,13 +3188,18 @@ ReturnValue Game::internalCollectManagedItems(const std::shared_ptr &pla ReturnValue Game::collectRewardChestItems(const std::shared_ptr &player, uint32_t maxMoveItems /* = 0*/) { // Check if have item on player reward chest - std::shared_ptr rewardChest = player->getRewardChest(); + const std::shared_ptr &rewardChest = player->getRewardChest(); if (rewardChest->empty()) { g_logger().debug("Reward chest is empty"); return RETURNVALUE_REWARDCHESTISEMPTY; } - auto rewardItemsVector = player->getRewardsFromContainer(rewardChest->getContainer()); + const auto &container = rewardChest->getContainer(); + if (!container) { + return RETURNVALUE_REWARDCHESTISEMPTY; + } + + auto rewardItemsVector = player->getRewardsFromContainer(container); auto rewardCount = rewardItemsVector.size(); uint32_t movedRewardItems = 0; std::string lootedItemsMessage; @@ -9931,19 +9940,72 @@ void Game::removePlayer(const std::shared_ptr &player) { } void Game::addNpc(const std::shared_ptr &npc) { - npcs[npc->getID()] = npc; + npcs.push_back(npc); + size_t index = npcs.size() - 1; + npcsNameIndex[npc->getLowerName()] = index; + npcsIdIndex[npc->getID()] = index; } void Game::removeNpc(const std::shared_ptr &npc) { - npcs.erase(npc->getID()); + if (!npc) { + return; + } + + auto npcId = npc->getID(); + const auto &npcLowerName = npc->getLowerName(); + auto it = npcsIdIndex.find(npcId); + if (it != npcsIdIndex.end()) { + size_t index = it->second; + npcsNameIndex.erase(npcLowerName); + npcsIdIndex.erase(npcId); + + if (index != npcs.size() - 1) { + std::swap(npcs[index], npcs.back()); + + const auto &movedNpc = npcs[index]; + npcsNameIndex[movedNpc->getLowerName()] = index; + npcsIdIndex[movedNpc->getID()] = index; + } + + npcs.pop_back(); + } } void Game::addMonster(const std::shared_ptr &monster) { - monsters[monster->getID()] = monster; + if (!monster) { + return; + } + + const auto &lowerName = monster->getLowerName(); + monsters.push_back(monster); + size_t index = monsters.size() - 1; + monstersNameIndex[lowerName] = index; + monstersIdIndex[monster->getID()] = index; } void Game::removeMonster(const std::shared_ptr &monster) { - monsters.erase(monster->getID()); + if (!monster) { + return; + } + + auto monsterId = monster->getID(); + const auto &monsterLowerName = monster->getLowerName(); + auto it = monstersIdIndex.find(monsterId); + if (it != monstersIdIndex.end()) { + size_t index = it->second; + monstersNameIndex.erase(monsterLowerName); + monstersIdIndex.erase(monsterId); + + if (index != monsters.size() - 1) { + std::swap(monsters[index], monsters.back()); + + const auto &movedMonster = monsters[index]; + monstersNameIndex[movedMonster->getLowerName()] = index; + monstersIdIndex[movedMonster->getID()] = index; + } + + monsters.pop_back(); + } } std::shared_ptr Game::getGuild(uint32_t id, bool allowOffline /* = flase */) const { @@ -10152,7 +10214,7 @@ uint32_t Game::makeFiendishMonster(uint32_t forgeableMonsterId /* = 0*/, bool cr forgeableMonsters.clear(); // If the forgeable monsters haven't been created // Then we'll create them so they don't return in the next if (forgeableMonsters.empty()) - for (const auto &[monsterId, monster] : monsters) { + for (const auto &monster : monsters) { auto monsterTile = monster->getTile(); if (!monster || !monsterTile) { continue; @@ -10325,7 +10387,7 @@ void Game::updateForgeableMonsters() { if (auto influencedLimit = g_configManager().getNumber(FORGE_INFLUENCED_CREATURES_LIMIT); forgeableMonsters.size() < influencedLimit) { forgeableMonsters.clear(); - for (const auto &[monsterId, monster] : monsters) { + for (const auto &monster : monsters) { const auto &monsterTile = monster->getTile(); if (!monsterTile) { continue; @@ -10541,7 +10603,7 @@ void Game::playerRewardChestCollect(uint32_t playerId, const Position &pos, uint } // Updates the parent of the reward chest and reward containers to avoid memory usage after cleaning - auto playerRewardChest = player->getRewardChest(); + const auto &playerRewardChest = player->getRewardChest(); if (playerRewardChest && playerRewardChest->empty()) { player->sendCancelMessage(RETURNVALUE_REWARDCHESTISEMPTY); return; diff --git a/src/game/game.hpp b/src/game/game.hpp index b16d1787d69..a59c7d13a70 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -522,10 +522,10 @@ class Game { const phmap::parallel_flat_hash_map> &getPlayers() const { return players; } - const std::map> &getMonsters() const { + const auto &getMonsters() const { return monsters; } - const std::map> &getNpcs() const { + const auto &getNpcs() const { return npcs; } @@ -539,8 +539,8 @@ class Game { void addNpc(const std::shared_ptr &npc); void removeNpc(const std::shared_ptr &npc); - void addMonster(const std::shared_ptr &npc); - void removeMonster(const std::shared_ptr &npc); + void addMonster(const std::shared_ptr &monster); + void removeMonster(const std::shared_ptr &monster); std::shared_ptr getGuild(uint32_t id, bool allowOffline = false) const; std::shared_ptr getGuildByName(const std::string &name, bool allowOffline = false) const; @@ -851,8 +851,16 @@ class Game { std::shared_ptr wildcardTree = nullptr; - std::map> npcs; - std::map> monsters; + std::vector> monsters; + // This works only for unique monsters (bosses, quest monsters, etc) + std::unordered_map monstersNameIndex; + std::unordered_map monstersIdIndex; + + std::vector> npcs; + // This works only for unique npcs (quest npcs, etc) + std::unordered_map npcsNameIndex; + std::unordered_map npcsIdIndex; + std::vector forgeableMonsters; std::map> teamFinderMap; // [leaderGUID] = TeamFinder* diff --git a/src/game/zones/zone.hpp b/src/game/zones/zone.hpp index a3af961bd7f..e13652e8b32 100644 --- a/src/game/zones/zone.hpp +++ b/src/game/zones/zone.hpp @@ -216,7 +216,7 @@ class Zone { Position removeDestination = Position(); std::string name; std::string monsterVariant; - std::unordered_set positions; + phmap::flat_hash_set positions; uint32_t id = 0; // ID 0 is used in zones created dynamically from lua. The map editor uses IDs starting from 1 (automatically generated). weak::set itemsCache; diff --git a/src/io/io_bosstiary.cpp b/src/io/io_bosstiary.cpp index 3e8a05524e6..57a9a85e294 100644 --- a/src/io/io_bosstiary.cpp +++ b/src/io/io_bosstiary.cpp @@ -20,46 +20,46 @@ IOBosstiary &IOBosstiary::getInstance() { } void IOBosstiary::loadBoostedBoss() { - Database &database = Database::getInstance(); - std::ostringstream query; - query << "SELECT * FROM `boosted_boss`"; - DBResult_ptr result = database.storeQuery(query.str()); - if (!result) { - g_logger().error("[{}] Failed to detect boosted boss database. (CODE 01)", __FUNCTION__); - return; - } - - auto date = result->getNumber("date"); + std::string query = R"SQL( + SELECT `date`, `boostname`, `raceid`, `looktypeEx`, `looktype`, + `lookfeet`, `looklegs`, `lookhead`, `lookbody`, + `lookaddons`, `lookmount` + FROM `boosted_boss` + )SQL"; + + DBResult_ptr result = g_database().storeQuery(query); auto timeNow = getTimeNow(); auto time = localtime(&timeNow); auto today = time->tm_mday; - auto bossMap = getBosstiaryMap(); + const auto &bossMap = getBosstiaryMap(); if (bossMap.size() <= 1) { g_logger().error("[{}] It is not possible to create a boosted boss with only one registered boss. (CODE 02)", __FUNCTION__); return; } - std::string bossName; - uint16_t bossId = 0; - if (date == today) { - bossName = result->getString("boostname"); - bossId = result->getNumber("raceid"); - setBossBoostedName(bossName); - setBossBoostedId(bossId); - g_logger().info("Boosted boss: {}", bossName); - return; + if (!result) { + g_logger().warn("[{}] No boosted boss found in g_database(). A new one will be selected.", __FUNCTION__); + } else { + auto date = result->getNumber("date"); + if (date == today) { + std::string bossName = result->getString("boostname"); + uint16_t bossId = result->getNumber("raceid"); + setBossBoostedName(bossName); + setBossBoostedId(bossId); + g_logger().info("Boosted boss: {}", bossName); + return; + } } // Filter only archfoe bosses - std::map bossInfo; - for (auto [infoBossRaceId, infoBossName] : bossMap) { - const auto mType = getMonsterTypeByBossRaceId(infoBossRaceId); + std::vector> bossInfo; + for (const auto &[infoBossRaceId, infoBossName] : bossMap) { + const auto &mType = getMonsterTypeByBossRaceId(infoBossRaceId); if (!mType || mType->info.bosstiaryRace != BosstiaryRarity_t::RARITY_ARCHFOE) { continue; } - - bossInfo.try_emplace(infoBossRaceId, infoBossName); + bossInfo.emplace_back(infoBossRaceId, infoBossName); } // Check if not have archfoe registered boss @@ -68,55 +68,39 @@ void IOBosstiary::loadBoostedBoss() { return; } - auto oldBossRace = result->getNumber("raceid"); - while (true) { - uint32_t randomIndex = uniform_random(0, static_cast(bossInfo.size())); - auto it = std::next(bossInfo.begin(), randomIndex); - if (it == bossInfo.end()) { - break; - } - - const auto &[randomBossId, randomBossName] = *it; - if (randomBossId == oldBossRace) { - continue; - } - - bossName = randomBossName; - bossId = randomBossId; - break; + const auto &[randomBossId, randomBossName] = bossInfo[uniform_random(0, static_cast(bossInfo.size() - 1))]; + std::string bossName = randomBossName; + uint16_t bossId = randomBossId; + + query = fmt::format( + "UPDATE `boosted_boss` SET `date` = '{}', `boostname` = {}, ", + today, g_database().escapeString(bossName) + ); + if (const auto &bossType = getMonsterTypeByBossRaceId(bossId); bossType) { + query += fmt::format( + "`looktypeEx` = {}, `looktype` = {}, `lookfeet` = {}, `looklegs` = {}, " + "`lookhead` = {}, `lookbody` = {}, `lookaddons` = {}, `lookmount` = {}, ", + bossType->info.outfit.lookTypeEx, bossType->info.outfit.lookType, + bossType->info.outfit.lookFeet, bossType->info.outfit.lookLegs, + bossType->info.outfit.lookHead, bossType->info.outfit.lookBody, + bossType->info.outfit.lookAddons, bossType->info.outfit.lookMount + ); } + query += fmt::format("`raceid` = {}", bossId); - query.str(std::string()); - query << "UPDATE `boosted_boss` SET "; - query << "`date` = '" << today << "',"; - query << "`boostname` = " << database.escapeString(bossName) << ","; - if (const auto bossType = getMonsterTypeByBossRaceId(bossId); - bossType) { - query << "`looktypeEx` = " << static_cast(bossType->info.outfit.lookTypeEx) << ","; - query << "`looktype` = " << static_cast(bossType->info.outfit.lookType) << ","; - query << "`lookfeet` = " << static_cast(bossType->info.outfit.lookFeet) << ","; - query << "`looklegs` = " << static_cast(bossType->info.outfit.lookLegs) << ","; - query << "`lookhead` = " << static_cast(bossType->info.outfit.lookHead) << ","; - query << "`lookbody` = " << static_cast(bossType->info.outfit.lookBody) << ","; - query << "`lookaddons` = " << static_cast(bossType->info.outfit.lookAddons) << ","; - query << "`lookmount` = " << static_cast(bossType->info.outfit.lookMount) << ","; - } - query << "`raceid` = '" << bossId << "'"; - if (!database.executeQuery(query.str())) { - g_logger().error("[{}] Failed to detect boosted boss database. (CODE 03)", __FUNCTION__); + if (!g_database().executeQuery(query)) { + g_logger().error("[{}] Failed to update boosted boss in g_database(). (CODE 03)", __FUNCTION__); return; } - query.str(std::string()); - query << "UPDATE `player_bosstiary` SET `bossIdSlotOne` = 0 WHERE `bossIdSlotOne` = " << bossId; - if (!database.executeQuery(query.str())) { - g_logger().error("[{}] Failed to reset players selected boss slot 1. (CODE 03)", __FUNCTION__); + query = fmt::format("UPDATE `player_bosstiary` SET `bossIdSlotOne` = 0 WHERE `bossIdSlotOne` = {}", bossId); + if (!g_database().executeQuery(query)) { + g_logger().error("[{}] Failed to reset players' selected boss slot 1. (CODE 03)", __FUNCTION__); } - query.str(std::string()); - query << "UPDATE `player_bosstiary` SET `bossIdSlotTwo` = 0 WHERE `bossIdSlotTwo` = " << bossId; - if (!database.executeQuery(query.str())) { - g_logger().error("[{}] Failed to reset players selected boss slot 1. (CODE 03)", __FUNCTION__); + query = fmt::format("UPDATE `player_bosstiary` SET `bossIdSlotTwo` = 0 WHERE `bossIdSlotTwo` = {}", bossId); + if (!g_database().executeQuery(query)) { + g_logger().error("[{}] Failed to reset players' selected boss slot 2. (CODE 03)", __FUNCTION__); } setBossBoostedName(bossName); diff --git a/src/lua/functions/creatures/monster/monster_type_functions.cpp b/src/lua/functions/creatures/monster/monster_type_functions.cpp index 07ce44b626c..8b2f4a5f314 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.cpp @@ -1170,18 +1170,22 @@ int MonsterTypeFunctions::luaMonsterTypeGetCreatureEvents(lua_State* L) { int MonsterTypeFunctions::luaMonsterTypeRegisterEvent(lua_State* L) { // monsterType:registerEvent(name) const auto &monsterType = Lua::getUserdataShared(L, 1); - if (monsterType) { - const auto eventName = Lua::getString(L, 2); - monsterType->info.scripts.insert(eventName); - for (const auto &[_, monster] : g_game().getMonsters()) { - if (monster->getMonsterType() == monsterType) { - monster->registerCreatureEvent(eventName); - } - } - Lua::pushBoolean(L, true); - } else { + if (!monsterType) { lua_pushnil(L); + return 1; } + + const auto eventName = Lua::getString(L, 2); + monsterType->info.scripts.insert(eventName); + + for (const auto &monster : g_game().getMonsters()) { + const auto monsterTypeCompare = monster->getMonsterType(); + if (monsterTypeCompare == monsterType) { + monster->registerCreatureEvent(eventName); + } + } + + Lua::pushBoolean(L, true); return 1; }