diff --git a/data/libs/functions/boss_lever.lua b/data/libs/functions/boss_lever.lua index 3792cdf629d..48d00582e89 100644 --- a/data/libs/functions/boss_lever.lua +++ b/data/libs/functions/boss_lever.lua @@ -174,24 +174,36 @@ function BossLever:onUse(player) end if creature:getLevel() < self.requiredLevel then - creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "All the players need to be level " .. self.requiredLevel .. " or higher.") + local message = "All players need to be level " .. self.requiredLevel .. " or higher." + creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) return false end - if self:lastEncounterTime(creature) > os.time() then - local info = lever:getInfoPositions() - for _, v in pairs(info) do - local newPlayer = v.creature - if newPlayer then - local timeLeft = self:lastEncounterTime(newPlayer) - os.time() - newPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You or a member in your team have to wait " .. getTimeInWords(timeLeft) .. " to face " .. self.name .. " again!") - if self:lastEncounterTime(newPlayer) > os.time() then - newPlayer:getPosition():sendMagicEffect(CONST_ME_POFF) + if creature:getGroup():getId() < GROUP_TYPE_GOD and self:lastEncounterTime(creature) > os.time() then + local infoPositions = lever:getInfoPositions() + for _, posInfo in pairs(infoPositions) do + local currentPlayer = posInfo.creature + if currentPlayer then + local lastEncounter = self:lastEncounterTime(currentPlayer) + local currentTime = os.time() + if lastEncounter and currentTime < lastEncounter then + local timeLeft = lastEncounter - currentTime + local timeMessage = getTimeInWords(timeLeft) .. " to face " .. monsterName .. " again!" + local message = "You have to wait " .. timeMessage + + if currentPlayer ~= player then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "A member in your team has to wait " .. timeMessage) + end + + currentPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) + currentPlayer:getPosition():sendMagicEffect(CONST_ME_POFF) end end end return false end + self.onUseExtra(creature) return true end) diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua index ba7398d9d3e..214aec77d43 100644 --- a/data/modules/scripts/gamestore/init.lua +++ b/data/modules/scripts/gamestore/init.lua @@ -247,6 +247,11 @@ function onRecvbyte(player, msg, byte) return player:sendCancelMessage("Store don't have offers for rookgaard citizen.") end + if player:isUIExhausted(250) then + player:sendCancelMessage("You are exhausted.") + return + end + if byte == GameStore.RecivedPackets.C_StoreEvent then elseif byte == GameStore.RecivedPackets.C_TransferCoins then parseTransferableCoins(player:getId(), msg) @@ -262,12 +267,6 @@ function onRecvbyte(player, msg, byte) parseRequestTransactionHistory(player:getId(), msg) end - if player:isUIExhausted(250) then - player:sendCancelMessage("You are exhausted.") - return false - end - - player:updateUIExhausted() return true end @@ -306,6 +305,7 @@ function parseTransferableCoins(playerId, msg) GameStore.insertHistory(accountId, GameStore.HistoryTypes.HISTORY_TYPE_NONE, player:getName() .. " transferred you this amount.", amount, GameStore.CoinType.Transferable) GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, "You transferred this amount to " .. reciver, -1 * amount, GameStore.CoinType.Transferable) openStore(playerId) + player:updateUIExhausted() end function parseOpenStore(playerId, msg) @@ -396,6 +396,7 @@ function parseRequestStoreOffers(playerId, msg) addPlayerEvent(sendShowStoreOffers, 250, playerId, searchResultsCategory) end + player:updateUIExhausted() end function parseBuyStoreOffer(playerId, msg) @@ -532,6 +533,8 @@ function parseBuyStoreOffer(playerId, msg) sendUpdatedStoreBalances(playerId) return addPlayerEvent(sendStorePurchaseSuccessful, 650, playerId, message) end + + player:updateUIExhausted() return true end @@ -540,11 +543,13 @@ function parseOpenTransactionHistory(playerId, msg) local page = 1 GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE = msg:getByte() sendStoreTransactionHistory(playerId, page, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE) + player:updateUIExhausted() end function parseRequestTransactionHistory(playerId, msg) local page = msg:getU32() sendStoreTransactionHistory(playerId, page + 1, GameStore.DefaultValues.DEFAULT_VALUE_ENTRIES_PER_PAGE) + player:updateUIExhausted() end local function getCategoriesRook() diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index 6c45fa3eaca..061ad2b9879 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -455,7 +455,11 @@ class Monster final : public Creature { void dropLoot(std::shared_ptr corpse, std::shared_ptr lastHitCreature) override; void getPathSearchParams(const std::shared_ptr &creature, FindPathParams &fpp) override; bool useCacheMap() const override { - return !randomStepping; + // return !randomStepping; + // As the map cache is done synchronously for each movement that a monster makes, it is better to disable it, + // as the pathfinder, which is one of the resources that uses this cache the most, + // is multithreding and thus the processing cost is divided between the threads. + return false; } friend class MonsterFunctions; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 2b10c0f77ee..2c92f694197 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1715,13 +1715,7 @@ void Player::onCreatureAppear(std::shared_ptr creature, bool isLogin) Creature::onCreatureAppear(creature, isLogin); if (isLogin && creature == getPlayer()) { - for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { - std::shared_ptr item = inventory[slot]; - if (item) { - item->startDecaying(); - g_moveEvents().onPlayerEquip(getPlayer(), item, static_cast(slot), false); - } - } + onEquipInventory(); // Refresh bosstiary tracker onLogin refreshCyclopediaMonsterTracker(true); @@ -1862,14 +1856,9 @@ void Player::onRemoveCreature(std::shared_ptr creature, bool isLogout) Creature::onRemoveCreature(creature, isLogout); if (auto player = getPlayer(); player == creature) { - for (uint8_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { - const auto item = inventory[slot]; - if (item) { - g_moveEvents().onPlayerDeEquip(getPlayer(), item, static_cast(slot)); - } - } - if (isLogout) { + onDeEquipInventory(); + if (m_party) { m_party->leaveParty(player); } @@ -2011,6 +2000,25 @@ void Player::onCreatureMove(const std::shared_ptr &creature, const std } } +void Player::onEquipInventory() { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + std::shared_ptr item = inventory[slot]; + if (item) { + item->startDecaying(); + g_moveEvents().onPlayerEquip(getPlayer(), item, static_cast(slot), false); + } + } +} + +void Player::onDeEquipInventory() { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + std::shared_ptr item = inventory[slot]; + if (item) { + g_moveEvents().onPlayerDeEquip(getPlayer(), item, static_cast(slot)); + } + } +} + // container void Player::onAddContainerItem(std::shared_ptr item) { checkTradeState(item); diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 947acedca54..802b4e6fb6e 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -1315,6 +1315,9 @@ class Player final : public Creature, public Cylinder, public Bankable { void onRemoveCreature(std::shared_ptr creature, bool isLogout) override; void onCreatureMove(const std::shared_ptr &creature, const std::shared_ptr &newTile, const Position &newPos, const std::shared_ptr &oldTile, const Position &oldPos, bool teleport) override; + void onEquipInventory(); + void onDeEquipInventory(); + void onAttackedCreatureDisappear(bool isLogout) override; void onFollowCreatureDisappear(bool isLogout) override; @@ -2559,8 +2562,7 @@ class Player final : public Creature, public Cylinder, public Bankable { } bool checkAutoLoot(bool isBoss) const { - const bool autoLoot = g_configManager().getBoolean(AUTOLOOT, __FUNCTION__); - if (!autoLoot) { + if (!g_configManager().getBoolean(AUTOLOOT, __FUNCTION__)) { return false; } if (g_configManager().getBoolean(VIP_SYSTEM_ENABLED, __FUNCTION__) && g_configManager().getBoolean(VIP_AUTOLOOT_VIP_ONLY, __FUNCTION__) && !isVip()) { @@ -2568,18 +2570,13 @@ class Player final : public Creature, public Cylinder, public Bankable { } auto featureKV = kv()->scoped("features")->get("autoloot"); - if (featureKV.has_value()) { - auto value = featureKV->getNumber(); - if (value == 2) { - return true; - } else if (value == 1) { - return !isBoss; - } else if (value == 0) { - return false; - } + auto value = featureKV.has_value() ? featureKV->getNumber() : 0; + if (value == 2) { + return true; + } else if (value == 1) { + return !isBoss; } - - return true; + return false; } QuickLootFilter_t getQuickLootFilter() const { diff --git a/src/game/game.cpp b/src/game/game.cpp index 57daddded2e..984e675407c 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6255,7 +6255,6 @@ void Game::checkCreatureWalk(uint32_t creatureId) { const auto &creature = getCreatureByID(creatureId); if (creature && creature->getHealth() > 0) { creature->onCreatureWalk(); - cleanup(); } } @@ -6318,7 +6317,6 @@ void Game::checkCreatures() { --end; } } - cleanup(); index = (index + 1) % EVENT_CREATURECOUNT; } @@ -7967,8 +7965,6 @@ void Game::shutdown() { map.spawnsNpc.clear(); raids.clear(); - cleanup(); - if (serviceManager) { serviceManager->stop(); } @@ -7980,16 +7976,6 @@ void Game::shutdown() { g_logger().info("Done!"); } -void Game::cleanup() { - for (auto it = browseFields.begin(); it != browseFields.end();) { - if (it->second.expired()) { - it = browseFields.erase(it); - } else { - ++it; - } - } -} - void Game::addBestiaryList(uint16_t raceid, std::string name) { auto it = BestiaryList.find(raceid); if (it != BestiaryList.end()) { diff --git a/src/game/game.hpp b/src/game/game.hpp index b4be8facd09..2de00410e5e 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -426,7 +426,6 @@ class Game { void updatePlayerHelpers(std::shared_ptr player); - void cleanup(); void shutdown(); void dieSafely(const std::string &errorMsg); void addBestiaryList(uint16_t raceid, std::string name); diff --git a/src/game/scheduling/dispatcher.cpp b/src/game/scheduling/dispatcher.cpp index 7cff69d66bf..dbbfc020be5 100644 --- a/src/game/scheduling/dispatcher.cpp +++ b/src/game/scheduling/dispatcher.cpp @@ -56,30 +56,51 @@ void Dispatcher::executeSerialEvents(std::vector &tasks) { } void Dispatcher::executeParallelEvents(std::vector &tasks, const uint8_t groupId) { - std::atomic_uint_fast64_t totalTaskSize = tasks.size(); - std::atomic_bool isTasksCompleted = false; + asyncWait(tasks.size(), [groupId, &tasks](size_t i) { + dispacherContext.type = DispatcherType::AsyncEvent; + dispacherContext.group = static_cast(groupId); + tasks[i].execute(); - for (const auto &task : tasks) { - threadPool.detach_task([groupId, &task, &isTasksCompleted, &totalTaskSize] { - dispacherContext.type = DispatcherType::AsyncEvent; - dispacherContext.group = static_cast(groupId); - dispacherContext.taskName = task.getContext(); + dispacherContext.reset(); + }); - task.execute(); + tasks.clear(); +} - dispacherContext.reset(); +void Dispatcher::asyncWait(size_t requestSize, std::function &&f) { + if (requestSize == 0) { + return; + } - totalTaskSize.fetch_sub(1); - if (totalTaskSize.load() == 0) { - isTasksCompleted.store(true); - isTasksCompleted.notify_one(); - } - }); + // This prevents an async call from running inside another async call. + if (asyncWaitDisabled) { + for (uint_fast64_t i = 0; i < requestSize; ++i) { + f(i); + } + return; } - isTasksCompleted.wait(false); + const auto &partitions = generatePartition(requestSize); + const auto pSize = partitions.size(); - tasks.clear(); + BS::multi_future retFuture; + + if (pSize > 1) { + asyncWaitDisabled = true; + const auto min = partitions[1].first; + const auto max = partitions[partitions.size() - 1].second; + retFuture = threadPool.submit_loop(min, max, [&f](const unsigned int i) { f(i); }); + } + + const auto &[min, max] = partitions[0]; + for (uint_fast64_t i = min; i < max; ++i) { + f(i); + } + + if (pSize > 1) { + retFuture.wait(); + asyncWaitDisabled = false; + } } void Dispatcher::executeEvents(const TaskGroup startGroup) { diff --git a/src/game/scheduling/dispatcher.hpp b/src/game/scheduling/dispatcher.hpp index a6cbc8dd6dd..94b284c9316 100644 --- a/src/game/scheduling/dispatcher.hpp +++ b/src/game/scheduling/dispatcher.hpp @@ -108,6 +108,7 @@ class Dispatcher { } void asyncEvent(std::function &&f, TaskGroup group = TaskGroup::GenericParallel); + void asyncWait(size_t size, std::function &&f); uint64_t asyncCycleEvent(uint32_t delay, std::function &&f, TaskGroup group = TaskGroup::GenericParallel) { return scheduleEvent( @@ -173,6 +174,22 @@ class Dispatcher { } } + std::vector> generatePartition(size_t size) const { + if (size == 0) { + return {}; + } + + std::vector> list; + list.reserve(threadPool.get_thread_count()); + + const auto size_per_block = std::ceil(size / static_cast(threadPool.get_thread_count())); + for (uint_fast64_t i = 0; i < size; i += size_per_block) { + list.emplace_back(i, std::min(size, i + size_per_block)); + } + + return list; + } + uint_fast64_t dispatcherCycle = 0; ThreadPool &threadPool; @@ -200,6 +217,8 @@ class Dispatcher { phmap::btree_multiset, Task::Compare> scheduledTasks; phmap::parallel_flat_hash_map_m> scheduledTasksRef; + bool asyncWaitDisabled = false; + friend class CanaryServer; }; diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index 63ce657741a..6c526a8500a 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -63,6 +63,10 @@ std::shared_ptr Container::create(std::shared_ptr tile) { Container::~Container() { if (getID() == ITEM_BROWSEFIELD) { + if (getParent() && getParent()->getTile()) { + g_game().browseFields.erase(getParent()->getTile()); + } + for (std::shared_ptr item : itemlist) { item->setParent(getParent()); } diff --git a/src/map/map.cpp b/src/map/map.cpp index 8b8cebeb539..82629aeae05 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -342,13 +342,35 @@ void Map::moveCreature(const std::shared_ptr &creature, const std::sha bool teleport = forceTeleport || !newTile->getGround() || !Position::areInRange<1, 1, 0>(oldPos, newPos); - auto spectators = Spectators() - .find(oldPos, true) - .find(newPos, true); + Spectators spectators; + if (!teleport && oldPos.z == newPos.z) { + int32_t minRangeX = MAP_MAX_VIEW_PORT_X; + int32_t maxRangeX = MAP_MAX_VIEW_PORT_X; + int32_t minRangeY = MAP_MAX_VIEW_PORT_Y; + int32_t maxRangeY = MAP_MAX_VIEW_PORT_Y; + + if (oldPos.y > newPos.y) { + ++minRangeY; + } else if (oldPos.y < newPos.y) { + ++maxRangeY; + } + + if (oldPos.x < newPos.x) { + ++maxRangeX; + } else if (oldPos.x > newPos.x) { + ++minRangeX; + } + + spectators.find(oldPos, true, minRangeX, maxRangeX, minRangeY, maxRangeY); + } else { + spectators.find(oldPos, true); + spectators.find(newPos, true); + } auto playersSpectators = spectators.filter(); std::vector oldStackPosVector; + oldStackPosVector.reserve(playersSpectators.size()); for (const auto &spec : playersSpectators) { if (spec->canSeeCreature(creature)) { oldStackPosVector.push_back(oldTile->getClientIndexOfCreature(spec->getPlayer(), creature)); diff --git a/src/map/spectators.cpp b/src/map/spectators.cpp index 36cce6d535b..10aaf0faf87 100644 --- a/src/map/spectators.cpp +++ b/src/map/spectators.cpp @@ -18,50 +18,27 @@ void Spectators::clearCache() { spectatorsCache.clear(); } -bool Spectators::contains(const std::shared_ptr &creature) { - return creatures.contains(creature); -} - -bool Spectators::erase(const std::shared_ptr &creature) { - return creatures.erase(creature); -} - Spectators Spectators::insert(const std::shared_ptr &creature) { if (creature) { - creatures.emplace(creature); + creatures.emplace_back(creature); } return *this; } -Spectators Spectators::insertAll(const SpectatorList &list) { +Spectators Spectators::insertAll(const CreatureVector &list) { if (!list.empty()) { - creatures.insertAll(list); - } - return *this; -} + const bool hasValue = !creatures.empty(); -Spectators Spectators::join(Spectators &anotherSpectators) { - return insertAll(anotherSpectators.creatures.data()); -} - -bool Spectators::empty() const noexcept { - return creatures.empty(); -} + creatures.insert(creatures.end(), list.begin(), list.end()); -size_t Spectators::size() noexcept { - return creatures.size(); -} - -CreatureVector::iterator Spectators::begin() noexcept { - return creatures.begin(); -} - -CreatureVector::iterator Spectators::end() noexcept { - return creatures.end(); -} - -const CreatureVector &Spectators::data() noexcept { - return creatures.data(); + // Remove duplicate + if (hasValue) { + std::unordered_set uset(creatures.begin(), creatures.end()); + creatures.clear(); + creatures.insert(creatures.end(), uset.begin(), uset.end()); + } + } + return *this; } bool Spectators::checkCache(const SpectatorsCache::FloorData &specData, bool onlyPlayers, const Position ¢erPos, bool checkDistance, bool multifloor, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY) { @@ -77,7 +54,7 @@ bool Spectators::checkCache(const SpectatorsCache::FloorData &specData, bool onl } if (checkDistance) { - SpectatorList spectators; + CreatureVector spectators; spectators.reserve(creatures.size()); for (const auto &creature : *list) { const auto &specPos = creature->getPosition(); @@ -176,7 +153,7 @@ Spectators Spectators::find(const Position ¢erPos, bool multifloor, bool onl const int32_t endx2 = x2 - (x2 & SECTOR_MASK); const int32_t endy2 = y2 - (y2 & SECTOR_MASK); - SpectatorList spectators; + CreatureVector spectators; spectators.reserve(std::max(MAP_MAX_VIEW_PORT_X, MAP_MAX_VIEW_PORT_Y) * 2); const MapSector* startSector = g_game().map.getMapSector(startx1, starty1); diff --git a/src/map/spectators.hpp b/src/map/spectators.hpp index 93526e05c93..9e998da2bfa 100644 --- a/src/map/spectators.hpp +++ b/src/map/spectators.hpp @@ -16,12 +16,10 @@ class Monster; class Npc; struct Position; -using SpectatorList = std::vector>; - struct SpectatorsCache { struct FloorData { - std::optional floor; - std::optional multiFloor; + std::optional floor; + std::optional multiFloor; }; int32_t minRangeX { 0 }; @@ -48,16 +46,39 @@ class Spectators { requires std::is_base_of_v Spectators filter(); - bool contains(const std::shared_ptr &creature); - bool erase(const std::shared_ptr &creature); Spectators insert(const std::shared_ptr &creature); - Spectators insertAll(const SpectatorList &list); - Spectators join(Spectators &anotherSpectators); - bool empty() const noexcept; - size_t size() noexcept; - CreatureVector::iterator begin() noexcept; - CreatureVector::iterator end() noexcept; - const CreatureVector &data() noexcept; + Spectators insertAll(const CreatureVector &list); + Spectators join(const Spectators &anotherSpectators) { + return insertAll(anotherSpectators.creatures); + } + + bool contains(const std::shared_ptr &creature) const { + return std::ranges::find(creatures, creature) != creatures.end(); + } + + bool erase(const std::shared_ptr &creature) { + return std::erase(creatures, creature) > 0; + } + + bool empty() const noexcept { + return creatures.empty(); + } + + size_t size() const noexcept { + return creatures.size(); + } + + auto begin() const noexcept { + return creatures.begin(); + } + + auto end() const noexcept { + return creatures.end(); + } + + const auto &data() const noexcept { + return creatures; + } private: static phmap::flat_hash_map spectatorsCache; @@ -65,7 +86,7 @@ class Spectators { Spectators find(const Position ¢erPos, bool multifloor = false, bool onlyPlayers = false, int32_t minRangeX = 0, int32_t maxRangeX = 0, int32_t minRangeY = 0, int32_t maxRangeY = 0); bool checkCache(const SpectatorsCache::FloorData &specData, bool onlyPlayers, const Position ¢erPos, bool checkDistance, bool multifloor, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY); - stdext::vector_set> creatures; + CreatureVector creatures; }; template diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index f869cc05251..cdbeb862be3 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -5852,6 +5852,8 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { msg.add(purchaseStatistics.highestPrice); msg.add(purchaseStatistics.lowestPrice); } + } else { + msg.addByte(0x00); } } else { msg.addByte(0x00); // send to old protocol ? @@ -5875,6 +5877,8 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { msg.add(saleStatistics.highestPrice); msg.add(saleStatistics.lowestPrice); } + } else { + msg.addByte(0x00); } } else { msg.addByte(0x00); // send to old protocol ?