Guest User

game.cpp

a guest
May 17th, 2021
73
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 254.94 KB | None | 0 0
  1. /**
  2.  * The Forgotten Server - a free and open-source MMORPG server emulator
  3.  * Copyright (C) 2019  Mark Samman <mark.samman@gmail.com>
  4.  *
  5.  * This program is free software; you can redistribute it and/or modify
  6.  * it under the terms of the GNU General Public License as published by
  7.  * the Free Software Foundation; either version 2 of the License, or
  8.  * (at your option) any later version.
  9.  *
  10.  * This program is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  * GNU General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License along
  16.  * with this program; if not, write to the Free Software Foundation, Inc.,
  17.  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18.  */
  19.  
  20. #include "otpch.h"
  21.  
  22. #include "pugicast.h"
  23.  
  24. #include "actions.h"
  25. #include "bed.h"
  26. #include "configmanager.h"
  27. #include "creature.h"
  28. #include "creatureevent.h"
  29. #include "databasetasks.h"
  30. #include "events.h"
  31. #include "game.h"
  32. #include "globalevent.h"
  33. #include "iologindata.h"
  34. #include "iomarket.h"
  35. #include "items.h"
  36. #include "monster.h"
  37. #include "movement.h"
  38. #include "scheduler.h"
  39. #include "server.h"
  40. #include "spells.h"
  41. #include "talkaction.h"
  42. #include "weapons.h"
  43. #include "scripts.h"
  44. #include "modules.h"
  45. #include "imbuements.h"
  46. #include "account.hpp"
  47. #include "webhook.h"
  48.  
  49.  
  50. extern ConfigManager g_config;
  51. extern Actions* g_actions;
  52. extern Chat* g_chat;
  53. extern TalkActions* g_talkActions;
  54. extern Spells* g_spells;
  55. extern Vocations g_vocations;
  56. extern GlobalEvents* g_globalEvents;
  57. extern CreatureEvents* g_creatureEvents;
  58. extern Events* g_events;
  59. extern Monsters g_monsters;
  60. extern MoveEvents* g_moveEvents;
  61. extern Weapons* g_weapons;
  62. extern Scripts* g_scripts;
  63. extern Modules* g_modules;
  64. extern Imbuements* g_imbuements;
  65.  
  66. Game::Game()
  67. {
  68.     offlineTrainingWindow.choices.emplace_back("Sword Fighting and Shielding", SKILL_SWORD);
  69.     offlineTrainingWindow.choices.emplace_back("Axe Fighting and Shielding", SKILL_AXE);
  70.     offlineTrainingWindow.choices.emplace_back("Club Fighting and Shielding", SKILL_CLUB);
  71.     offlineTrainingWindow.choices.emplace_back("Distance Fighting and Shielding", SKILL_DISTANCE);
  72.     offlineTrainingWindow.choices.emplace_back("Magic Level and Shielding", SKILL_MAGLEVEL);
  73.     offlineTrainingWindow.buttons.emplace_back("Okay", 1);
  74.     offlineTrainingWindow.buttons.emplace_back("Cancel", 0);
  75.     offlineTrainingWindow.defaultEscapeButton = 1;
  76.     offlineTrainingWindow.defaultEnterButton = 0;
  77.     offlineTrainingWindow.priority = true;
  78. }
  79.  
  80. Game::~Game()
  81. {
  82.     for (const auto& it : guilds) {
  83.         delete it.second;
  84.     }
  85. }
  86.  
  87. void Game::loadBoostedCreature()
  88. {
  89.     Database& db = Database::getInstance();
  90.     std::ostringstream query;
  91.     query << "SELECT * FROM `boosted_creature`";
  92.     DBResult_ptr result = db.storeQuery(query.str());
  93.  
  94.     if (!result) {
  95.         SPDLOG_WARN("[Game::loadBoostedCreature] - "
  96.                     "Failed to detect boosted creature database. (CODE 01)");
  97.         return;
  98.     }
  99.  
  100.     uint16_t date = result->getNumber<uint16_t>("date");
  101.     std::string name = "";
  102.     time_t now = time(0);
  103.     tm *ltm = localtime(&now);
  104.     uint8_t today = ltm->tm_mday;
  105.     if (date == today) {
  106.         name = result->getString("boostname");
  107.     } else {
  108.         uint16_t oldrace = result->getNumber<uint16_t>("raceid");
  109.         std::map<uint16_t, std::string> monsterlist = getBestiaryList();
  110.         uint16_t newrace = 0;
  111.         uint8_t k = 1;
  112.         while (newrace == 0 || newrace == oldrace) {
  113.             uint16_t random = normal_random(0, monsterlist.size());
  114.             for (auto it : monsterlist) {
  115.                 if (k == random) {
  116.                     newrace = it.first;
  117.                     name = it.second;
  118.                 }
  119.                 k++;
  120.             }
  121.         }
  122.  
  123.         MonsterType* monsterType = g_monsters.getMonsterTypeByRaceId(newrace);
  124.  
  125.         query.str(std::string());
  126.         query << "UPDATE `boosted_creature` SET ";
  127.         query << "`date` = '" << ltm->tm_mday << "',";
  128.         query << "`boostname` = " << db.escapeString(name) << ",";
  129.  
  130.         if (monsterType) {
  131.             query << "`looktype` = " << static_cast<int>(monsterType->info.outfit.lookType) << ",";
  132.             query << "`lookfeet` = " << static_cast<int>(monsterType->info.outfit.lookFeet) << ",";
  133.             query << "`looklegs` = " << static_cast<int>(monsterType->info.outfit.lookLegs) << ",";
  134.             query << "`lookhead` = " << static_cast<int>(monsterType->info.outfit.lookHead) << ",";
  135.             query << "`lookbody` = " << static_cast<int>(monsterType->info.outfit.lookBody) << ",";
  136.             query << "`lookaddons` = " << static_cast<int>(monsterType->info.outfit.lookAddons) << ",";
  137.             query << "`lookmount` = " << static_cast<int>(monsterType->info.outfit.lookMount) << ",";
  138.         }
  139.  
  140.         query << "`raceid` = '" << newrace << "'";
  141.  
  142.         if (!db.executeQuery(query.str())) {
  143.             SPDLOG_WARN("[Game::loadBoostedCreature] - "
  144.                         "Failed to detect boosted creature database. (CODE 02)");
  145.             return;
  146.         }
  147.     }
  148.     setBoostedName(name);
  149.     SPDLOG_INFO("Boosted creature: {}", name);
  150. }
  151.  
  152. void Game::start(ServiceManager* manager)
  153. {
  154.     serviceManager = manager;
  155.  
  156.     time_t now = time(0);
  157.     const tm* tms = localtime(&now);
  158.     int minutes = tms->tm_min;
  159.     lightHour = (minutes * LIGHT_DAY_LENGTH) / 60;
  160.  
  161.     g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL_MS, std::bind(&Game::checkLight, this)));
  162.     g_scheduler.addEvent(createSchedulerTask(EVENT_CREATURE_THINK_INTERVAL, std::bind(&Game::checkCreatures, this, 0)));
  163.     g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this)));
  164.     g_scheduler.addEvent(createSchedulerTask(EVENT_IMBUEMENTINTERVAL, std::bind(&Game::checkImbuements, this)));
  165. }
  166.  
  167. GameState_t Game::getGameState() const
  168. {
  169.     return gameState;
  170. }
  171.  
  172. void Game::setWorldType(WorldType_t type)
  173. {
  174.     worldType = type;
  175. }
  176.  
  177. bool Game::loadScheduleEventFromXml()
  178. {
  179.     pugi::xml_document doc;
  180.     pugi::xml_parse_result result = doc.load_file("data/XML/events.xml");
  181.     if (!result) {
  182.         printXMLError("Error - Game::loadScheduleEventFromXml", "data/XML/events.xml", result);
  183.         return false;
  184.     }
  185.  
  186.         int16_t daysNow;
  187.         time_t t = time(NULL);
  188.         tm* timePtr = localtime(&t);
  189.         int16_t daysMath = ((timePtr->tm_year + 1900) * 365) + ((timePtr->tm_mon + 1) * 30) + (timePtr->tm_mday);
  190.  
  191.     for (auto schedNode : doc.child("events").children()) {
  192.         std::string ss_d;
  193.         std::stringstream ss;
  194.  
  195.         pugi::xml_attribute attr;
  196.         if ((attr = schedNode.attribute("name"))) {
  197.             ss_d = attr.as_string();
  198.             ss << "> " << ss_d << " event";
  199.         }
  200.  
  201.         int year;
  202.         int day;
  203.         int month;
  204.  
  205.         if (!(attr = schedNode.attribute("enddate"))) {
  206.             continue;
  207.         } else {
  208.             ss_d = attr.as_string();
  209.             sscanf(ss_d.c_str(), "%d/%d/%d", &month, &day, &year);
  210.             daysNow = ((year * 365) + (month * 30) + day);
  211.             if (daysMath > daysNow) {
  212.                 continue;
  213.             }
  214.         }
  215.  
  216.         if (!(attr = schedNode.attribute("startdate"))) {
  217.             continue;
  218.         } else {
  219.             ss_d = attr.as_string();
  220.             sscanf(ss_d.c_str(), "%d/%d/%d", &month, &day, &year);
  221.             daysNow = ((year * 365) + (month * 30) + day);
  222.             if (daysMath < daysNow) {
  223.                 continue;
  224.             }
  225.         }
  226.  
  227.         if ((attr = schedNode.attribute("script"))) {
  228.             if (!(g_scripts->loadEventSchedulerScripts(attr.as_string()))) {
  229.                 SPDLOG_WARN("Can not load the file '{}' on '/events/scripts/scheduler/'",
  230.                     attr.as_string());
  231.                 return false;
  232.             }
  233.         }
  234.  
  235.         for (auto schedENode : schedNode.children()) {
  236.             if ((schedENode.attribute("exprate"))) {
  237.                 uint16_t exprate = pugi::cast<uint16_t>(schedENode.attribute("exprate").value());
  238.                 g_game.setExpSchedule(exprate);
  239.                 ss << " exp: " << (exprate - 100) << "%";
  240.             }
  241.  
  242.             if ((schedENode.attribute("lootrate"))) {
  243.                 uint16_t lootrate = pugi::cast<uint16_t>(schedENode.attribute("lootrate").value());
  244.                 g_game.setLootSchedule(lootrate);
  245.                 ss << ", loot: " << (lootrate - 100) << "%";
  246.             }
  247.  
  248.             if ((schedENode.attribute("spawnrate"))) {
  249.                 uint32_t spawnrate = pugi::cast<uint32_t>(schedENode.attribute("spawnrate").value());
  250.                 g_game.setSpawnSchedule(spawnrate);
  251.                 ss << ", spawn: "  << (spawnrate - 100) << "%";
  252.             }
  253.  
  254.             if ((schedENode.attribute("skillrate"))) {
  255.                 uint16_t skillrate = pugi::cast<uint16_t>(schedENode.attribute("skillrate").value());
  256.                 g_game.setSkillSchedule(skillrate);
  257.                 ss << ", skill: " << (skillrate - 100) << "%";
  258.             }
  259.         }
  260.         SPDLOG_INFO(ss.str());
  261.     }
  262.     return true;
  263. }
  264.  
  265. void Game::setGameState(GameState_t newState)
  266. {
  267.     if (gameState == GAME_STATE_SHUTDOWN) {
  268.         return; //this cannot be stopped
  269.     }
  270.  
  271.     if (gameState == newState) {
  272.         return;
  273.     }
  274.  
  275.     gameState = newState;
  276.     switch (newState) {
  277.         case GAME_STATE_INIT: {
  278.             loadItemsPrice();
  279.  
  280.             groups.load();
  281.             g_chat->load();
  282.  
  283.             map.spawns.startup();
  284.  
  285.             raids.loadFromXml();
  286.             raids.startup();
  287.  
  288.             mounts.loadFromXml();
  289.  
  290.             if (!g_config.getBoolean(ConfigManager::STOREMODULES)) {
  291.                 gameStore.loadFromXml();
  292.                 gameStore.startup();
  293.             }
  294.  
  295.             loadMotdNum();
  296.             loadPlayersRecord();
  297.  
  298.             g_globalEvents->startup();
  299.             break;
  300.         }
  301.  
  302.         case GAME_STATE_SHUTDOWN: {
  303.             g_globalEvents->execute(GLOBALEVENT_SHUTDOWN);
  304.  
  305.             //kick all players that are still online
  306.             auto it = players.begin();
  307.             while (it != players.end()) {
  308.                 it->second->kickPlayer(true);
  309.                 it = players.begin();
  310.             }
  311.  
  312.             saveMotdNum();
  313.             saveGameState();
  314.  
  315.             g_dispatcher.addTask(
  316.                 createTask(std::bind(&Game::shutdown, this)));
  317.  
  318.             g_scheduler.stop();
  319.             g_databaseTasks.stop();
  320.             g_dispatcher.stop();
  321.             break;
  322.         }
  323.  
  324.         case GAME_STATE_CLOSED: {
  325.             /* kick all players without the CanAlwaysLogin flag */
  326.             auto it = players.begin();
  327.             while (it != players.end()) {
  328.                 if (!it->second->hasFlag(PlayerFlag_CanAlwaysLogin)) {
  329.                     it->second->kickPlayer(true);
  330.                     it = players.begin();
  331.                 } else {
  332.                     ++it;
  333.                 }
  334.             }
  335.  
  336.             saveGameState();
  337.             break;
  338.         }
  339.  
  340.         default:
  341.             break;
  342.     }
  343. }
  344.  
  345. void Game::onPressHotkeyEquip(Player* player, uint16_t spriteid)
  346. {
  347.     Item* item;
  348.     const ItemType& itemType = Item::items.getItemIdByClientId(spriteid);
  349.  
  350.     if (itemType.id == 0) {
  351.         return;
  352.     }
  353.  
  354.     bool removed = false;
  355.     ReturnValue ret = RETURNVALUE_NOERROR;
  356.  
  357.     if (itemType.weaponType == WEAPON_AMMO) {
  358.         Thing* quiverThing = player->getThing(CONST_SLOT_RIGHT);
  359.         Thing* backpackThing = player->getThing(CONST_SLOT_BACKPACK);
  360.         if (quiverThing && backpackThing) {
  361.             Item* quiver = quiverThing->getItem();
  362.             Item* backpack = backpackThing->getItem();
  363.             if (quiver && quiver->getWeaponType() == WEAPON_QUIVER && backpack) {
  364.                 item = findItemOfType(backpack->getContainer(), itemType.id);
  365.                 if (item) {
  366.                     ret = internalMoveItem(item->getParent(), quiver->getContainer(), 0, item, item->getItemCount(), nullptr);
  367.                 }
  368.                 else {
  369.                     ret = RETURNVALUE_NOTPOSSIBLE;
  370.                 }
  371.             }
  372.             else {
  373.                 ret = RETURNVALUE_NOTPOSSIBLE;
  374.             }
  375.         }
  376.     } else {
  377.         item = findItemOfType(player, itemType.id);
  378.  
  379.         if (!item) {
  380.             item = findItemOfType(player, itemType.transformEquipTo);
  381.             if (!item) {
  382.                 item = findItemOfType(player, itemType.transformDeEquipTo);
  383.             }
  384.             if (!item) {
  385.                 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  386.                 return;
  387.             }
  388.         }
  389.  
  390.         const ItemType& newitemType = Item::items[item->getID()];
  391.  
  392.         if (newitemType.id == 0) {
  393.             return;
  394.         }
  395.  
  396.         int32_t slotP = newitemType.slotPosition;
  397.         if (itemType.weaponType == WEAPON_SHIELD || itemType.weaponType == WEAPON_QUIVER) {
  398.             slotP = CONST_SLOT_RIGHT;
  399.         }
  400.         else if (hasBitSet(SLOTP_HEAD, slotP)) {
  401.             slotP = CONST_SLOT_HEAD;
  402.         }
  403.         else if (hasBitSet(SLOTP_RING, slotP)) {
  404.             slotP = CONST_SLOT_RING;
  405.         }
  406.         else if (hasBitSet(SLOTP_NECKLACE, slotP)) {
  407.             slotP = CONST_SLOT_NECKLACE;
  408.         }
  409.         else if (hasBitSet(SLOTP_ARMOR, slotP)) {
  410.             slotP = CONST_SLOT_ARMOR;
  411.         }
  412.         else if (hasBitSet(SLOTP_LEGS, slotP)) {
  413.             slotP = CONST_SLOT_LEGS;
  414.         }
  415.         else if (hasBitSet(SLOTP_FEET, slotP)) {
  416.             slotP = CONST_SLOT_FEET;
  417.         }
  418.         else if (hasBitSet(SLOTP_AMMO, slotP)) {
  419.             slotP = CONST_SLOT_AMMO;
  420.         }
  421.         else if (hasBitSet(SLOTP_LEFT, slotP) && !hasBitSet(SLOTP_TWO_HAND, slotP)) {
  422.             slotP = CONST_SLOT_LEFT;
  423.         }
  424.  
  425.         if (hasBitSet(SLOTP_TWO_HAND, slotP)) {
  426.             Thing* leftthing = player->getThing(CONST_SLOT_LEFT);
  427.             if (leftthing) {
  428.                 Item* slotLeft_item = leftthing->getItem();
  429.                 if (slotLeft_item) {
  430.                     if (slotLeft_item->getID() == item->getID()) {
  431.                         removed = true;
  432.                     }
  433.                     ret = internalMoveItem(slotLeft_item->getParent(), player, 0, slotLeft_item, slotLeft_item->getItemCount(), nullptr);
  434.                 }
  435.             }
  436.             Thing* rightthing = player->getThing(CONST_SLOT_RIGHT);
  437.             if (rightthing) {
  438.                 Item* slotRight_Item = rightthing->getItem();
  439.                 if (slotRight_Item) {
  440.                     if (newitemType.weaponType != WEAPON_DISTANCE || slotRight_Item->getWeaponType() != WEAPON_QUIVER)
  441.                         ret = internalMoveItem(slotRight_Item->getParent(), player, 0, slotRight_Item, slotRight_Item->getItemCount(), nullptr);
  442.                 }
  443.                 else {
  444.                     return;
  445.                 }
  446.             }
  447.             if (!removed) {
  448.                 ret = internalMoveItem(item->getParent(), player, CONST_SLOT_LEFT, item, item->getItemCount(), nullptr);
  449.             }
  450.         }
  451.         else if (hasBitSet(SLOTP_RING, slotP)) {
  452.             Thing* ringthing = player->getThing(CONST_SLOT_RING);
  453.             if (ringthing) {
  454.                 Item* slotRing_Item = ringthing->getItem();
  455.                 if (slotRing_Item) {
  456.                     ret = internalMoveItem(slotRing_Item->getParent(), player, 0, slotRing_Item, slotRing_Item->getItemCount(), nullptr);
  457.                     if (slotRing_Item->getID() == item->getID()) {
  458.                         removed = true;
  459.                     }
  460.                 }
  461.                 else {
  462.                     return;
  463.                 }
  464.             }
  465.             if (!removed) {
  466.                 ret = internalMoveItem(item->getParent(), player, CONST_SLOT_RING, item, item->getItemCount(), nullptr);
  467.             }
  468.         }
  469.         else if (slotP == CONST_SLOT_RIGHT) {
  470.             Thing* rightthing = player->getThing(CONST_SLOT_RIGHT);
  471.             if (rightthing) {
  472.                 Item* slotRight_Item = rightthing->getItem();
  473.                 if (slotRight_Item) {
  474.                     if (slotRight_Item->getID() == item->getID()) {
  475.                         removed = true;
  476.                     }
  477.                     ret = internalMoveItem(slotRight_Item->getParent(), player, 0, slotRight_Item, slotRight_Item->getItemCount(), nullptr);
  478.                 }
  479.                 else {
  480.                     return;
  481.                 }
  482.             }
  483.             Thing* leftthing = player->getThing(CONST_SLOT_LEFT);
  484.             if (leftthing) {
  485.                 Item* slotLeft_item = leftthing->getItem();
  486.                 if (slotLeft_item) {
  487.                     ItemType& it = Item::items.getItemType(slotLeft_item->getID());
  488.                     if (hasBitSet(SLOTP_TWO_HAND, it.slotPosition)) {
  489.                         if (newitemType.weaponType != WEAPON_QUIVER || slotLeft_item->getWeaponType() != WEAPON_DISTANCE)
  490.                             ret = internalMoveItem(slotLeft_item->getParent(), player, 0, slotLeft_item, slotLeft_item->getItemCount(), nullptr);
  491.                     }
  492.                 }
  493.                 else {
  494.                     return;
  495.                 }
  496.             }
  497.             if (!removed) {
  498.                 ret = internalMoveItem(item->getParent(), player, slotP, item, item->getItemCount(), nullptr);
  499.             }
  500.         }
  501.         else if (slotP) {
  502.             if (newitemType.stackable) {
  503.                 Thing* ammothing = player->getThing(slotP);
  504.                 if (ammothing) {
  505.                     Item* ammoItem = ammothing->getItem();
  506.                     if (ammoItem) {
  507.                         ObjectCategory_t category = getObjectCategory(ammoItem);
  508.                         if (ammoItem->getID() == item->getID()) {
  509.                             if (item->getDuration() > 0 ||
  510.                                 ammoItem->getItemCount() == 100 ||
  511.                                 ammoItem->getItemCount() == player->getItemTypeCount(ammoItem->getID())) {
  512.                                 ret = internalQuickLootItem(player, ammoItem, category);
  513.                                 if (ret != RETURNVALUE_NOERROR) {
  514.                                     ret = internalMoveItem(ammoItem->getParent(), player, 0, ammoItem, ammoItem->getItemCount(), nullptr);
  515.                                 }
  516.                                 if (ret != RETURNVALUE_NOERROR) {
  517.                                     player->sendCancelMessage(ret);
  518.                                 }
  519.                                 return;
  520.                             }
  521.                         }
  522.                         else {
  523.                             ret = internalQuickLootItem(player, ammoItem, category);
  524.                             if (ret != RETURNVALUE_NOERROR) {
  525.                                 ret = internalMoveItem(ammoItem->getParent(), player, 0, ammoItem, ammoItem->getItemCount(), nullptr);
  526.                             }
  527.                         }
  528.                     }
  529.                     else {
  530.                         return;
  531.                     }
  532.                 }
  533.                 ReturnValue ret2 = player->queryAdd(slotP, *item, item->getItemCount(), 0);
  534.                 if (ret2 != RETURNVALUE_NOERROR) {
  535.                     player->sendCancelMessage(ret2);
  536.                     return;
  537.                 }
  538.                 if (item->getItemCount() < 100 &&
  539.                     item->getItemCount() < player->getItemTypeCount(item->getID(), -1) &&
  540.                     item->getDuration() <= 0) {
  541.                     uint16_t itemId = item->getID();
  542.                     uint16_t count = 0;
  543.                     while (player->getItemTypeCount(item->getID())) {
  544.                         if (count == 100) {
  545.                             break;
  546.                         }
  547.                         Container* mainBP = player->getInventoryItem(CONST_SLOT_BACKPACK)->getContainer();
  548.                         Item* _item = findItemOfType(mainBP, itemId);
  549.  
  550.                         if (!_item) {
  551.                             break;
  552.                         }
  553.  
  554.                         if (_item->getItemCount() > 100 - count) {
  555.                             internalRemoveItem(_item, 100 - count);
  556.                             count = 100;
  557.                         }
  558.                         else {
  559.                             count = count + _item->getItemCount();
  560.                             internalRemoveItem(_item, _item->getItemCount());
  561.                         }
  562.                     }
  563.                     Item* newSlotitem = Item::CreateItem(itemId, count);
  564.                     internalAddItem(player, newSlotitem, slotP, FLAG_NOLIMIT);
  565.                     return;
  566.                 }
  567.                 else {
  568.                     ret = internalMoveItem(item->getParent(), player, slotP, item, item->getItemCount(), nullptr);
  569.                 }
  570.             }
  571.             else {
  572.                 Thing* slotthing = player->getThing(slotP);
  573.                 if (slotthing) {
  574.                     Item* slotItem = slotthing->getItem();
  575.                     if (slotItem) {
  576.                         ret = internalMoveItem(slotItem->getParent(), player, 0, slotItem, slotItem->getItemCount(), nullptr);
  577.                         if (slotItem->getID() == item->getID()) {
  578.                             removed = true;
  579.                         }
  580.                     }
  581.                     else {
  582.                         return;
  583.                     }
  584.                 }
  585.                 if (!removed) {
  586.                     ret = internalMoveItem(item->getParent(), player, slotP, item, item->getItemCount(), nullptr);
  587.                 }
  588.             }
  589.         }
  590.     }
  591.  
  592.     if (ret != RETURNVALUE_NOERROR) {
  593.       player->sendCancelMessage(ret);
  594.     }
  595.     return;
  596. }
  597.  
  598. void Game::saveGameState()
  599. {
  600.     if (gameState == GAME_STATE_NORMAL) {
  601.         setGameState(GAME_STATE_MAINTAIN);
  602.     }
  603.  
  604.     SPDLOG_INFO("Saving server...");
  605.  
  606.     for (const auto& it : players) {
  607.         it.second->loginPosition = it.second->getPosition();
  608.         IOLoginData::savePlayer(it.second);
  609.     }
  610.  
  611.   for (const auto& it : guilds) {
  612.     IOGuild::saveGuild(it.second);
  613.   }
  614.  
  615.     Map::save();
  616.  
  617.     g_databaseTasks.flush();
  618.  
  619.     if (gameState == GAME_STATE_MAINTAIN) {
  620.         setGameState(GAME_STATE_NORMAL);
  621.     }
  622. }
  623.  
  624. bool Game::loadItemsPrice()
  625. {
  626.     itemsSaleCount = 0;
  627.     std::ostringstream query, query2;
  628.     query << "SELECT DISTINCT `itemtype` FROM `market_offers`;";
  629.  
  630.     Database& db = Database::getInstance();
  631.     DBResult_ptr result = db.storeQuery(query.str());
  632.     if (!result) {
  633.         return false;
  634.     }
  635.  
  636.     do {
  637.         query2.str(std::string());
  638.         uint16_t itemId = result->getNumber<uint16_t>("itemtype");
  639.         query2 << "SELECT `price` FROM `market_offers` WHERE `itemtype` = " << itemId << " ORDER BY `price` DESC LIMIT 1";
  640.         DBResult_ptr resultQuery2 = db.storeQuery(query2.str());
  641.         if (resultQuery2) {
  642.             itemsPriceMap[itemId] = resultQuery2->getNumber<uint32_t>("price");
  643.             itemsSaleCount++;
  644.         }
  645.  
  646.     } while (result->next());
  647.  
  648.  
  649.     return true;
  650. }
  651.  
  652. bool Game::loadMainMap(const std::string& filename)
  653. {
  654.     Monster::despawnRange = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRANGE);
  655.     Monster::despawnRadius = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRADIUS);
  656.     return map.loadMap("data/world/" + filename + ".otbm", true, true);
  657. }
  658.  
  659. void Game::loadMap(const std::string& path)
  660. {
  661.     map.loadMap(path, false, false);
  662. }
  663.  
  664. bool Game::loadCustomSpawnFile(const std::string& fileName)
  665. {
  666.     return map.spawns.loadCustomSpawnXml(fileName);
  667. }
  668.  
  669. Cylinder* Game::internalGetCylinder(Player* player, const Position& pos) const
  670. {
  671.     if (pos.x != 0xFFFF) {
  672.         return map.getTile(pos);
  673.     }
  674.  
  675.     //container
  676.     if (pos.y & 0x40) {
  677.         uint8_t from_cid = pos.y & 0x0F;
  678.         return player->getContainerByID(from_cid);
  679.     }
  680.  
  681.     //inventory
  682.     return player;
  683. }
  684.  
  685. Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index, uint32_t spriteId, stackPosType_t type) const
  686. {
  687.     if (pos.x != 0xFFFF) {
  688.         Tile* tile = map.getTile(pos);
  689.         if (!tile) {
  690.             return nullptr;
  691.         }
  692.  
  693.         Thing* thing;
  694.         switch (type) {
  695.             case STACKPOS_LOOK: {
  696.                 return tile->getTopVisibleThing(player);
  697.             }
  698.  
  699.             case STACKPOS_MOVE: {
  700.                 Item* item = tile->getTopDownItem();
  701.                 if (item && item->isMoveable()) {
  702.                     thing = item;
  703.                 } else {
  704.                     thing = tile->getTopVisibleCreature(player);
  705.                 }
  706.                 break;
  707.             }
  708.  
  709.             case STACKPOS_USEITEM: {
  710.                 thing = tile->getUseItem(index);
  711.                 break;
  712.             }
  713.  
  714.             case STACKPOS_TOPDOWN_ITEM: {
  715.                 thing = tile->getTopDownItem();
  716.                 break;
  717.             }
  718.  
  719.             case STACKPOS_USETARGET: {
  720.                 thing = tile->getTopVisibleCreature(player);
  721.                 if (!thing) {
  722.                     thing = tile->getUseItem(index);
  723.                 }
  724.                 break;
  725.             }
  726.  
  727.             case STACKPOS_FIND_THING: {
  728.                 thing = tile->getUseItem(index);
  729.                 if (!thing) {
  730.                     thing = tile->getDoorItem();
  731.                 }
  732.  
  733.                 if (!thing) {
  734.                     thing = tile->getTopDownItem();
  735.                 }
  736.  
  737.                 break;
  738.             }
  739.  
  740.             default: {
  741.                 thing = nullptr;
  742.                 break;
  743.             }
  744.         }
  745.  
  746.         if (player && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) {
  747.             //do extra checks here if the thing is accessable
  748.             if (thing && thing->getItem()) {
  749.                 if (tile->hasProperty(CONST_PROP_ISVERTICAL)) {
  750.                     if (player->getPosition().x + 1 == tile->getPosition().x && thing->getItem()->isHangable()) {
  751.                         thing = nullptr;
  752.                     }
  753.                 } else { // horizontal
  754.                     if (player->getPosition().y + 1 == tile->getPosition().y && thing->getItem()->isHangable()) {
  755.                         thing = nullptr;
  756.                     }
  757.                 }
  758.             }
  759.         }
  760.         return thing;
  761.     }
  762.  
  763.     //container
  764.     if (pos.y & 0x40) {
  765.         uint8_t fromCid = pos.y & 0x0F;
  766.  
  767.         Container* parentContainer = player->getContainerByID(fromCid);
  768.         if (!parentContainer) {
  769.             return nullptr;
  770.         }
  771.  
  772.         if (parentContainer->getID() == ITEM_BROWSEFIELD) {
  773.             Tile* tile = parentContainer->getTile();
  774.             if (tile && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) {
  775.                 if (tile->hasProperty(CONST_PROP_ISVERTICAL)) {
  776.                     if (player->getPosition().x + 1 == tile->getPosition().x) {
  777.                         return nullptr;
  778.                     }
  779.                 } else { // horizontal
  780.                     if (player->getPosition().y + 1 == tile->getPosition().y) {
  781.                         return nullptr;
  782.                     }
  783.                 }
  784.             }
  785.         }
  786.  
  787.         uint8_t slot = pos.z;
  788.         return parentContainer->getItemByIndex(player->getContainerIndex(fromCid) + slot);
  789.     } else if (pos.y == 0 && pos.z == 0) {
  790.         const ItemType& it = Item::items.getItemIdByClientId(spriteId);
  791.         if (it.id == 0) {
  792.             return nullptr;
  793.         }
  794.  
  795.         int32_t subType;
  796.         if (it.isFluidContainer() && index < static_cast<int32_t>(sizeof(reverseFluidMap) / sizeof(uint8_t))) {
  797.             subType = reverseFluidMap[index];
  798.         } else {
  799.             subType = -1;
  800.         }
  801.  
  802.         return findItemOfType(player, it.id, true, subType);
  803.     }
  804.  
  805.     //inventory
  806.     slots_t slot = static_cast<slots_t>(pos.y);
  807.     return player->getInventoryItem(slot);
  808. }
  809.  
  810. void Game::internalGetPosition(Item* item, Position& pos, uint8_t& stackpos)
  811. {
  812.     pos.x = 0;
  813.     pos.y = 0;
  814.     pos.z = 0;
  815.     stackpos = 0;
  816.  
  817.     Cylinder* topParent = item->getTopParent();
  818.     if (topParent) {
  819.         if (Player* player = dynamic_cast<Player*>(topParent)) {
  820.             pos.x = 0xFFFF;
  821.  
  822.             Container* container = dynamic_cast<Container*>(item->getParent());
  823.             if (container) {
  824.                 pos.y = static_cast<uint16_t>(0x40) | static_cast<uint16_t>(player->getContainerID(container));
  825.                 pos.z = container->getThingIndex(item);
  826.                 stackpos = pos.z;
  827.             } else {
  828.                 pos.y = player->getThingIndex(item);
  829.                 stackpos = pos.y;
  830.             }
  831.         } else if (Tile* tile = topParent->getTile()) {
  832.             pos = tile->getPosition();
  833.             stackpos = tile->getThingIndex(item);
  834.         }
  835.     }
  836. }
  837.  
  838. Creature* Game::getCreatureByID(uint32_t id)
  839. {
  840.     if (id <= Player::playerAutoID) {
  841.         return getPlayerByID(id);
  842.     } else if (id <= Monster::monsterAutoID) {
  843.         return getMonsterByID(id);
  844.     } else if (id <= Npc::npcAutoID) {
  845.         return getNpcByID(id);
  846.     }
  847.     return nullptr;
  848. }
  849.  
  850. Monster* Game::getMonsterByID(uint32_t id)
  851. {
  852.     if (id == 0) {
  853.         return nullptr;
  854.     }
  855.  
  856.     auto it = monsters.find(id);
  857.     if (it == monsters.end()) {
  858.         return nullptr;
  859.     }
  860.     return it->second;
  861. }
  862.  
  863. Npc* Game::getNpcByID(uint32_t id)
  864. {
  865.     if (id == 0) {
  866.         return nullptr;
  867.     }
  868.  
  869.     auto it = npcs.find(id);
  870.     if (it == npcs.end()) {
  871.         return nullptr;
  872.     }
  873.     return it->second;
  874. }
  875.  
  876. Player* Game::getPlayerByID(uint32_t id)
  877. {
  878.     if (id == 0) {
  879.         return nullptr;
  880.     }
  881.  
  882.     auto it = players.find(id);
  883.     if (it == players.end()) {
  884.         return nullptr;
  885.     }
  886.     return it->second;
  887. }
  888.  
  889. Creature* Game::getCreatureByName(const std::string& s)
  890. {
  891.     if (s.empty()) {
  892.         return nullptr;
  893.     }
  894.  
  895.     const std::string& lowerCaseName = asLowerCaseString(s);
  896.  
  897.     auto m_it = mappedPlayerNames.find(lowerCaseName);
  898.     if (m_it != mappedPlayerNames.end()) {
  899.         return m_it->second;
  900.     }
  901.  
  902.     for (const auto& it : npcs) {
  903.         if (lowerCaseName == asLowerCaseString(it.second->getName())) {
  904.             return it.second;
  905.         }
  906.     }
  907.  
  908.     for (const auto& it : monsters) {
  909.         if (lowerCaseName == asLowerCaseString(it.second->getName())) {
  910.             return it.second;
  911.         }
  912.     }
  913.     return nullptr;
  914. }
  915.  
  916. Npc* Game::getNpcByName(const std::string& s)
  917. {
  918.     if (s.empty()) {
  919.         return nullptr;
  920.     }
  921.  
  922.     const char* npcName = s.c_str();
  923.     for (const auto& it : npcs) {
  924.         if (strcasecmp(npcName, it.second->getName().c_str()) == 0) {
  925.             return it.second;
  926.         }
  927.     }
  928.     return nullptr;
  929. }
  930.  
  931. Player* Game::getPlayerByName(const std::string& s)
  932. {
  933.     if (s.empty()) {
  934.         return nullptr;
  935.     }
  936.  
  937.     auto it = mappedPlayerNames.find(asLowerCaseString(s));
  938.     if (it == mappedPlayerNames.end()) {
  939.         return nullptr;
  940.     }
  941.     return it->second;
  942. }
  943.  
  944. Player* Game::getPlayerByGUID(const uint32_t& guid)
  945. {
  946.     if (guid == 0) {
  947.         return nullptr;
  948.     }
  949.  
  950.     for (const auto& it : players) {
  951.         if (guid == it.second->getGUID()) {
  952.             return it.second;
  953.         }
  954.     }
  955.     return nullptr;
  956. }
  957.  
  958. ReturnValue Game::getPlayerByNameWildcard(const std::string& s, Player*& player)
  959. {
  960.     size_t strlen = s.length();
  961.     if (strlen == 0 || strlen > 20) {
  962.         return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE;
  963.     }
  964.  
  965.     if (s.back() == '~') {
  966.         const std::string& query = asLowerCaseString(s.substr(0, strlen - 1));
  967.         std::string result;
  968.         ReturnValue ret = wildcardTree.findOne(query, result);
  969.         if (ret != RETURNVALUE_NOERROR) {
  970.             return ret;
  971.         }
  972.  
  973.         player = getPlayerByName(result);
  974.     } else {
  975.         player = getPlayerByName(s);
  976.     }
  977.  
  978.     if (!player) {
  979.         return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE;
  980.     }
  981.  
  982.     return RETURNVALUE_NOERROR;
  983. }
  984.  
  985. Player* Game::getPlayerByAccount(uint32_t acc)
  986. {
  987.     for (const auto& it : players) {
  988.         if (it.second->getAccount() == acc) {
  989.             return it.second;
  990.         }
  991.     }
  992.     return nullptr;
  993. }
  994.  
  995. bool Game::internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/)
  996. {
  997.     if (creature->getParent() != nullptr) {
  998.         return false;
  999.     }
  1000.  
  1001.     if (!map.placeCreature(pos, creature, extendedPos, forced)) {
  1002.         return false;
  1003.     }
  1004.  
  1005.     creature->incrementReferenceCounter();
  1006.     creature->setID();
  1007.     creature->addList();
  1008.     return true;
  1009. }
  1010.  
  1011. bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/)
  1012. {
  1013.     if (!internalPlaceCreature(creature, pos, extendedPos, forced)) {
  1014.         return false;
  1015.     }
  1016.  
  1017.     SpectatorHashSet spectators;
  1018.     map.getSpectators(spectators, creature->getPosition(), true);
  1019.     for (Creature* spectator : spectators) {
  1020.         if (Player* tmpPlayer = spectator->getPlayer()) {
  1021.             tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true);
  1022.         }
  1023.     }
  1024.  
  1025.     for (Creature* spectator : spectators) {
  1026.         spectator->onCreatureAppear(creature, true);
  1027.     }
  1028.  
  1029.     creature->getParent()->postAddNotification(creature, nullptr, 0);
  1030.  
  1031.     addCreatureCheck(creature);
  1032.     creature->onPlacedCreature();
  1033.     return true;
  1034. }
  1035.  
  1036. bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/)
  1037. {
  1038.     if (creature->isRemoved()) {
  1039.         return false;
  1040.     }
  1041.  
  1042.     Tile* tile = creature->getTile();
  1043.  
  1044.     std::vector<int32_t> oldStackPosVector;
  1045.  
  1046.     SpectatorHashSet spectators;
  1047.     map.getSpectators(spectators, tile->getPosition(), true);
  1048.     for (Creature* spectator : spectators) {
  1049.         if (Player* player = spectator->getPlayer()) {
  1050.             oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1);
  1051.         }
  1052.     }
  1053.  
  1054.     tile->removeCreature(creature);
  1055.  
  1056.     const Position& tilePosition = tile->getPosition();
  1057.  
  1058.     //send to client
  1059.     size_t i = 0;
  1060.     for (Creature* spectator : spectators) {
  1061.         if (Player* player = spectator->getPlayer()) {
  1062.             player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]);
  1063.         }
  1064.     }
  1065.  
  1066.     //event method
  1067.     for (Creature* spectator : spectators) {
  1068.         spectator->onRemoveCreature(creature, isLogout);
  1069.     }
  1070.  
  1071.   if (creature->getMaster() && !creature->getMaster()->isRemoved()) {
  1072.     creature->setMaster(nullptr);
  1073.   }
  1074.  
  1075.     creature->getParent()->postRemoveNotification(creature, nullptr, 0);
  1076.  
  1077.     creature->removeList();
  1078.     creature->setRemoved();
  1079.     ReleaseCreature(creature);
  1080.  
  1081.     removeCreatureCheck(creature);
  1082.  
  1083.     for (Creature* summon : creature->summons) {
  1084.         summon->setSkillLoss(false);
  1085.         removeCreature(summon);
  1086.     }
  1087.  
  1088.     if (creature->getPlayer() && isLogout) {
  1089.         auto it = teamFinderMap.find(creature->getPlayer()->getGUID());
  1090.         if (it != teamFinderMap.end()) {
  1091.             teamFinderMap.erase(it);
  1092.         }
  1093.     }
  1094.  
  1095.     return true;
  1096. }
  1097.  
  1098. void Game::executeDeath(uint32_t creatureId)
  1099. {
  1100.     Creature* creature = getCreatureByID(creatureId);
  1101.     if (creature && !creature->isRemoved()) {
  1102.         creature->onDeath();
  1103.     }
  1104. }
  1105.  
  1106. void Game::playerTeleport(uint32_t playerId, const Position& newPosition) {
  1107.   Player* player = getPlayerByID(playerId);
  1108.   if (!player || !player->hasCustomFlag(PlayerCustomFlag_CanMapClickTeleport)) {
  1109.     return;
  1110.   }
  1111.  
  1112.   ReturnValue returnValue = g_game.internalTeleport(player, newPosition, false);
  1113.   if (returnValue != RETURNVALUE_NOERROR) {
  1114.     player->sendCancelMessage(returnValue);
  1115.   }
  1116. }
  1117.  
  1118. void Game::playerInspectItem(Player* player, const Position& pos) {
  1119.     Thing* thing = internalGetThing(player, pos, 0, 0, STACKPOS_TOPDOWN_ITEM);
  1120.     if (!thing) {
  1121.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1122.         return;
  1123.     }
  1124.  
  1125.     Item* item = thing->getItem();
  1126.     if (!item) {
  1127.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1128.         return;
  1129.     }
  1130.  
  1131.     player->sendItemInspection(item->getClientID(), item->getItemCount(), item, false);
  1132. }
  1133.  
  1134. void Game::playerInspectItem(Player* player, uint16_t itemId, uint8_t itemCount, bool cyclopedia) {
  1135.     player->sendItemInspection(itemId, itemCount, nullptr, cyclopedia);
  1136. }
  1137.  
  1138. void Game::playerMoveThing(uint32_t playerId, const Position& fromPos,
  1139.                            uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count)
  1140. {
  1141.     Player* player = getPlayerByID(playerId);
  1142.     if (!player) {
  1143.         return;
  1144.     }
  1145.  
  1146.     uint8_t fromIndex = 0;
  1147.     if (fromPos.x == 0xFFFF) {
  1148.         if (fromPos.y & 0x40) {
  1149.             fromIndex = fromPos.z;
  1150.         } else {
  1151.             fromIndex = static_cast<uint8_t>(fromPos.y);
  1152.         }
  1153.     } else {
  1154.         fromIndex = fromStackPos;
  1155.     }
  1156.  
  1157.     Thing* thing = internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE);
  1158.     if (!thing) {
  1159.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1160.         return;
  1161.     }
  1162.  
  1163.     if (Creature* movingCreature = thing->getCreature()) {
  1164.         Tile* tile = map.getTile(toPos);
  1165.         if (!tile) {
  1166.             player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1167.             return;
  1168.         }
  1169.  
  1170.         if (Position::areInRange<1, 1, 0>(movingCreature->getPosition(), player->getPosition())) {
  1171.             SchedulerTask* task = createSchedulerTask(g_config.getNumber(ConfigManager::PUSH_DELAY), std::bind(&Game::playerMoveCreatureByID, this, player->getID(),
  1172.                                               movingCreature->getID(), movingCreature->getPosition(), tile->getPosition()));
  1173.             player->setNextActionPushTask(task);
  1174.         } else {
  1175.             playerMoveCreature(player, movingCreature, movingCreature->getPosition(), tile);
  1176.         }
  1177.     } else if (thing->getItem()) {
  1178.         Cylinder* toCylinder = internalGetCylinder(player, toPos);
  1179.         if (!toCylinder) {
  1180.             player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1181.             return;
  1182.         }
  1183.  
  1184.         playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, thing->getItem(), toCylinder);
  1185.     }
  1186. }
  1187.  
  1188. void Game::playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, const Position& toPos)
  1189. {
  1190.     Player* player = getPlayerByID(playerId);
  1191.     if (!player) {
  1192.         return;
  1193.     }
  1194.  
  1195.     Creature* movingCreature = getCreatureByID(movingCreatureId);
  1196.     if (!movingCreature) {
  1197.         return;
  1198.     }
  1199.  
  1200.     Tile* toTile = map.getTile(toPos);
  1201.     if (!toTile) {
  1202.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1203.         return;
  1204.     }
  1205.  
  1206.     playerMoveCreature(player, movingCreature, movingCreatureOrigPos, toTile);
  1207. }
  1208.  
  1209. void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile)
  1210. {
  1211.     if (!player->canDoAction()) {
  1212.         uint32_t delay = 600;
  1213.         SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveCreatureByID,
  1214.             this, player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition()));
  1215.  
  1216.         player->setNextActionPushTask(task);
  1217.         return;
  1218.     }
  1219.  
  1220.     player->setNextActionTask(nullptr);
  1221.  
  1222.     if (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos, player->getPosition())) {
  1223.         //need to walk to the creature first before moving it
  1224.         std::forward_list<Direction> listDir;
  1225.         if (player->getPathTo(movingCreatureOrigPos, listDir, 0, 1, true, true)) {
  1226.             g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk,
  1227.                                             this, player->getID(), listDir)));
  1228.  
  1229.             SchedulerTask* task = createSchedulerTask(600, std::bind(&Game::playerMoveCreatureByID, this,
  1230.                 player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition()));
  1231.  
  1232.             player->pushEvent(true);
  1233.             player->setNextActionPushTask(task);
  1234.         } else {
  1235.             player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  1236.         }
  1237.         return;
  1238.     }
  1239.  
  1240.     player->pushEvent(false);
  1241.     const Monster* monster = movingCreature->getMonster();
  1242.     bool isPet = false;
  1243.     if (monster) {
  1244.         isPet = monster->isPet();
  1245.     }
  1246.  
  1247.     if (!isPet && ((!movingCreature->isPushable() && !player->hasFlag(PlayerFlag_CanPushAllCreatures)) ||
  1248.                 (movingCreature->isInGhostMode() && !player->isAccessPlayer()))) {
  1249.         player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE);
  1250.         return;
  1251.     }
  1252.  
  1253.     //check throw distance
  1254.     const Position& movingCreaturePos = movingCreature->getPosition();
  1255.     const Position& toPos = toTile->getPosition();
  1256.     if ((Position::getDistanceX(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceY(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceZ(movingCreaturePos, toPos) * 4 > movingCreature->getThrowRange())) {
  1257.         player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH);
  1258.         return;
  1259.     }
  1260.  
  1261.     if (player != movingCreature) {
  1262.         if (toTile->hasFlag(TILESTATE_BLOCKPATH)) {
  1263.             player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
  1264.             return;
  1265.         } else if ((movingCreature->getZone() == ZONE_PROTECTION && !toTile->hasFlag(TILESTATE_PROTECTIONZONE)) || (movingCreature->getZone() == ZONE_NOPVP && !toTile->hasFlag(TILESTATE_NOPVPZONE))) {
  1266.             player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1267.             return;
  1268.         } else {
  1269.             if (CreatureVector* tileCreatures = toTile->getCreatures()) {
  1270.                 for (Creature* tileCreature : *tileCreatures) {
  1271.                     if (!tileCreature->isInGhostMode()) {
  1272.                         player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
  1273.                         return;
  1274.                     }
  1275.                 }
  1276.             }
  1277.  
  1278.             Npc* movingNpc = movingCreature->getNpc();
  1279.             if (movingNpc && !Spawns::isInZone(movingNpc->getMasterPos(), movingNpc->getMasterRadius(), toPos)) {
  1280.                 player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
  1281.                 return;
  1282.             }
  1283.         }
  1284.  
  1285.         movingCreature->setLastPosition(movingCreature->getPosition());
  1286.     }
  1287.  
  1288.     if (!g_events->eventPlayerOnMoveCreature(player, movingCreature, movingCreaturePos, toPos)) {
  1289.         return;
  1290.     }
  1291.  
  1292.     ReturnValue ret = internalMoveCreature(*movingCreature, *toTile);
  1293.     if (ret != RETURNVALUE_NOERROR) {
  1294.         player->sendCancelMessage(ret);
  1295.     }
  1296.     player->setLastPosition(player->getPosition());
  1297. }
  1298.  
  1299. ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, uint32_t flags /*= 0*/)
  1300. {
  1301.     creature->setLastPosition(creature->getPosition());
  1302.     const Position& currentPos = creature->getPosition();
  1303.     Position destPos = getNextPosition(direction, currentPos);
  1304.     Player* player = creature->getPlayer();
  1305.  
  1306.     bool diagonalMovement = (direction & DIRECTION_DIAGONAL_MASK) != 0;
  1307.     if (player && !diagonalMovement) {
  1308.         //try go up
  1309.         if (currentPos.z != 8 && creature->getTile()->hasHeight(3)) {
  1310.             Tile* tmpTile = map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1);
  1311.             if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) {
  1312.                 tmpTile = map.getTile(destPos.x, destPos.y, destPos.getZ() - 1);
  1313.                 if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)) {
  1314.                     flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE;
  1315.  
  1316.                     if (!tmpTile->hasFlag(TILESTATE_FLOORCHANGE)) {
  1317.                         player->setDirection(direction);
  1318.                         destPos.z--;
  1319.                     }
  1320.                 }
  1321.             }
  1322.         }
  1323.  
  1324.         //try go down
  1325.         if (currentPos.z != 7 && currentPos.z == destPos.z) {
  1326.             Tile* tmpTile = map.getTile(destPos.x, destPos.y, destPos.z);
  1327.             if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) {
  1328.                 tmpTile = map.getTile(destPos.x, destPos.y, destPos.z + 1);
  1329.                 if (tmpTile && tmpTile->hasHeight(3)) {
  1330.                     flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE;
  1331.                     player->setDirection(direction);
  1332.                     destPos.z++;
  1333.                 }
  1334.             }
  1335.         }
  1336.     }
  1337.  
  1338.     Tile* toTile = map.getTile(destPos);
  1339.     if (!toTile) {
  1340.         return RETURNVALUE_NOTPOSSIBLE;
  1341.     }
  1342.     return internalMoveCreature(*creature, *toTile, flags);
  1343. }
  1344.  
  1345. ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags /*= 0*/)
  1346. {
  1347.     if (creature.hasCondition(CONDITION_ROOTED)) {
  1348.         return RETURNVALUE_NOTPOSSIBLE;
  1349.     }
  1350.  
  1351.     //check if we can move the creature to the destination
  1352.     ReturnValue ret = toTile.queryAdd(0, creature, 1, flags);
  1353.     if (ret != RETURNVALUE_NOERROR) {
  1354.         return ret;
  1355.     }
  1356.  
  1357.     map.moveCreature(creature, toTile);
  1358.     if (creature.getParent() != &toTile) {
  1359.         return RETURNVALUE_NOERROR;
  1360.     }
  1361.  
  1362.     int32_t index = 0;
  1363.     Item* toItem = nullptr;
  1364.     Tile* subCylinder = nullptr;
  1365.     Tile* toCylinder = &toTile;
  1366.     Tile* fromCylinder = nullptr;
  1367.     uint32_t n = 0;
  1368.  
  1369.     while ((subCylinder = toCylinder->queryDestination(index, creature, &toItem, flags)) != toCylinder) {
  1370.         map.moveCreature(creature, *subCylinder);
  1371.  
  1372.         if (creature.getParent() != subCylinder) {
  1373.             //could happen if a script move the creature
  1374.             fromCylinder = nullptr;
  1375.             break;
  1376.         }
  1377.  
  1378.         fromCylinder = toCylinder;
  1379.         toCylinder = subCylinder;
  1380.         flags = 0;
  1381.  
  1382.         //to prevent infinite loop
  1383.         if (++n >= MAP_MAX_LAYERS) {
  1384.             break;
  1385.         }
  1386.     }
  1387.  
  1388.     if (fromCylinder) {
  1389.         const Position& fromPosition = fromCylinder->getPosition();
  1390.         const Position& toPosition = toCylinder->getPosition();
  1391.         if (fromPosition.z != toPosition.z && (fromPosition.x != toPosition.x || fromPosition.y != toPosition.y)) {
  1392.             Direction dir = getDirectionTo(fromPosition, toPosition);
  1393.             if ((dir & DIRECTION_DIAGONAL_MASK) == 0) {
  1394.                 internalCreatureTurn(&creature, dir);
  1395.             }
  1396.         }
  1397.     }
  1398.  
  1399.     return RETURNVALUE_NOERROR;
  1400. }
  1401.  
  1402. void Game::playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count)
  1403. {
  1404.     Player* player = getPlayerByID(playerId);
  1405.     if (!player) {
  1406.         return;
  1407.     }
  1408.     playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, nullptr, nullptr);
  1409. }
  1410.  
  1411. void Game::playerMoveItem(Player* player, const Position& fromPos,
  1412.                           uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder)
  1413. {
  1414.     if (!player->canDoAction()) {
  1415.         uint32_t delay = player->getNextActionTime();
  1416.         SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveItemByPlayerID, this,
  1417.                               player->getID(), fromPos, spriteId, fromStackPos, toPos, count));
  1418.         player->setNextActionTask(task);
  1419.         return;
  1420.     }
  1421.  
  1422.     player->setNextActionTask(nullptr);
  1423.  
  1424.     if (item == nullptr) {
  1425.         uint8_t fromIndex = 0;
  1426.         if (fromPos.x == 0xFFFF) {
  1427.             if (fromPos.y & 0x40) {
  1428.                 fromIndex = fromPos.z;
  1429.             } else {
  1430.                 fromIndex = static_cast<uint8_t>(fromPos.y);
  1431.             }
  1432.         } else {
  1433.             fromIndex = fromStackPos;
  1434.         }
  1435.  
  1436.         Thing* thing = internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE);
  1437.         if (!thing || !thing->getItem()) {
  1438.             player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1439.             return;
  1440.         }
  1441.  
  1442.         item = thing->getItem();
  1443.     }
  1444.  
  1445.     if (item->getClientID() != spriteId) {
  1446.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1447.         return;
  1448.     }
  1449.  
  1450.     Cylinder* fromCylinder = internalGetCylinder(player, fromPos);
  1451.     if (fromCylinder == nullptr) {
  1452.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1453.         return;
  1454.     }
  1455.  
  1456.     if (toCylinder == nullptr) {
  1457.         toCylinder = internalGetCylinder(player, toPos);
  1458.         if (toCylinder == nullptr) {
  1459.             player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1460.             return;
  1461.         }
  1462.     }
  1463.  
  1464.     if (!item->isPushable() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
  1465.         player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE);
  1466.         return;
  1467.     }
  1468.  
  1469.     const Position& playerPos = player->getPosition();
  1470.     const Position& mapFromPos = fromCylinder->getTile()->getPosition();
  1471.     if (playerPos.z != mapFromPos.z) {
  1472.         player->sendCancelMessage(playerPos.z > mapFromPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS);
  1473.         return;
  1474.     }
  1475.  
  1476.     if (!Position::areInRange<1, 1>(playerPos, mapFromPos)) {
  1477.         //need to walk to the item first before using it
  1478.         std::forward_list<Direction> listDir;
  1479.         if (player->getPathTo(item->getPosition(), listDir, 0, 1, true, true)) {
  1480.             g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk,
  1481.                                             this, player->getID(), listDir)));
  1482.  
  1483.             SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this,
  1484.                                   player->getID(), fromPos, spriteId, fromStackPos, toPos, count));
  1485.             player->setNextWalkActionTask(task);
  1486.         } else {
  1487.             player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  1488.         }
  1489.         return;
  1490.     }
  1491.  
  1492.     const Tile* toCylinderTile = toCylinder->getTile();
  1493.     const Position& mapToPos = toCylinderTile->getPosition();
  1494.  
  1495.     //hangable item specific code
  1496.     if (item->isHangable() && toCylinderTile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) {
  1497.         //destination supports hangable objects so need to move there first
  1498.         bool vertical = toCylinderTile->hasProperty(CONST_PROP_ISVERTICAL);
  1499.         if (vertical) {
  1500.             if (playerPos.x + 1 == mapToPos.x) {
  1501.                 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1502.                 return;
  1503.             }
  1504.         } else { // horizontal
  1505.             if (playerPos.y + 1 == mapToPos.y) {
  1506.                 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  1507.                 return;
  1508.             }
  1509.         }
  1510.  
  1511.         if (!Position::areInRange<1, 1, 0>(playerPos, mapToPos)) {
  1512.             Position walkPos = mapToPos;
  1513.             if (vertical) {
  1514.                 walkPos.x++;
  1515.             } else {
  1516.                 walkPos.y++;
  1517.             }
  1518.  
  1519.             Position itemPos = fromPos;
  1520.             uint8_t itemStackPos = fromStackPos;
  1521.  
  1522.             if (fromPos.x != 0xFFFF && Position::areInRange<1, 1>(mapFromPos, playerPos)
  1523.                     && !Position::areInRange<1, 1, 0>(mapFromPos, walkPos)) {
  1524.                 //need to pickup the item first
  1525.                 Item* moveItem = nullptr;
  1526.  
  1527.                 ReturnValue ret = internalMoveItem(fromCylinder, player, INDEX_WHEREEVER, item, count, &moveItem);
  1528.                 if (ret != RETURNVALUE_NOERROR) {
  1529.                     player->sendCancelMessage(ret);
  1530.                     return;
  1531.                 }
  1532.  
  1533.                 //changing the position since its now in the inventory of the player
  1534.                 internalGetPosition(moveItem, itemPos, itemStackPos);
  1535.             }
  1536.  
  1537.             std::forward_list<Direction> listDir;
  1538.             if (player->getPathTo(walkPos, listDir, 0, 0, true, true)) {
  1539.                 g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk,
  1540.                                                 this, player->getID(), listDir)));
  1541.  
  1542.                 SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this,
  1543.                                       player->getID(), itemPos, spriteId, itemStackPos, toPos, count));
  1544.                 player->setNextWalkActionTask(task);
  1545.             } else {
  1546.                 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  1547.             }
  1548.             return;
  1549.         }
  1550.     }
  1551.  
  1552.     if ((Position::getDistanceX(playerPos, mapToPos) > item->getThrowRange()) ||
  1553.             (Position::getDistanceY(playerPos, mapToPos) > item->getThrowRange()) ||
  1554.             (Position::getDistanceZ(mapFromPos, mapToPos) * 4 > item->getThrowRange())) {
  1555.         player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH);
  1556.         return;
  1557.     }
  1558.  
  1559.     if (toCylinder->getContainer() != NULL &&
  1560.         toCylinder->getItem()->getID() == ITEM_LOCKER1 &&
  1561.         toPos.getZ() == ITEM_SUPPLY_STASH_INDEX) {
  1562.         player->stowItem(item, count, false);
  1563.             return;
  1564.     }
  1565.  
  1566.     if (!canThrowObjectTo(mapFromPos, mapToPos)) {
  1567.         player->sendCancelMessage(RETURNVALUE_CANNOTTHROW);
  1568.         return;
  1569.     }
  1570.  
  1571.     if (!g_events->eventPlayerOnMoveItem(player, item, count, fromPos, toPos, fromCylinder, toCylinder)) {
  1572.         return;
  1573.     }
  1574.  
  1575.     uint8_t toIndex = 0;
  1576.     if (toPos.x == 0xFFFF) {
  1577.         if (toPos.y & 0x40) {
  1578.             toIndex = toPos.z;
  1579.         } else {
  1580.             toIndex = static_cast<uint8_t>(toPos.y);
  1581.         }
  1582.     }
  1583.  
  1584.     if (item->isWrapable()){
  1585.         HouseTile* toHouseTile = dynamic_cast<HouseTile*>(map.getTile(mapToPos));
  1586.         HouseTile* fromHouseTile = dynamic_cast<HouseTile*>(map.getTile(mapFromPos));
  1587.         if (fromHouseTile && (!toHouseTile || toHouseTile->getHouse()->getId() != fromHouseTile->getHouse()->getId())) {
  1588.             player->sendCancelMessage("You can't move this item outside a house.");
  1589.                 return;
  1590.         }
  1591.     }
  1592.     ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr, 0, player);
  1593.     if (ret != RETURNVALUE_NOERROR) {
  1594.         player->sendCancelMessage(ret);
  1595.     }   else if (toCylinder->getContainer()
  1596.         && fromCylinder->getContainer()
  1597.         && fromCylinder->getContainer()->countsToLootAnalyzerBalance()
  1598.         && toCylinder->getContainer()->getTopParent() == player) {
  1599.         player->sendLootStats(item, count);
  1600.     }
  1601.     player->cancelPush();
  1602.  
  1603.     g_events->eventPlayerOnItemMoved(player, item, count, fromPos, toPos, fromCylinder, toCylinder);
  1604. }
  1605.  
  1606. ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index,
  1607.                                    Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, Creature* actor/* = nullptr*/, Item* tradeItem/* = nullptr*/)
  1608. {
  1609.     Tile* fromTile = fromCylinder->getTile();
  1610.     if (fromTile) {
  1611.         auto it = browseFields.find(fromTile);
  1612.         if (it != browseFields.end() && it->second == fromCylinder) {
  1613.             fromCylinder = fromTile;
  1614.         }
  1615.     }
  1616.  
  1617.     Item* toItem = nullptr;
  1618.  
  1619.     Cylinder* subCylinder;
  1620.     int floorN = 0;
  1621.  
  1622.     while ((subCylinder = toCylinder->queryDestination(index, *item, &toItem, flags)) != toCylinder) {
  1623.         toCylinder = subCylinder;
  1624.         flags = 0;
  1625.  
  1626.         //to prevent infinite loop
  1627.         if (++floorN >= MAP_MAX_LAYERS) {
  1628.             break;
  1629.         }
  1630.     }
  1631.  
  1632.     //destination is the same as the source?
  1633.     if (item == toItem) {
  1634.         return RETURNVALUE_NOERROR; //silently ignore move
  1635.     }
  1636.  
  1637.     // 'Move up' stackable items fix
  1638.     //  Cip's client never sends the count of stackables when using "Move up" menu option
  1639.     if (item->isStackable() && count == 255 && fromCylinder->getParent() == toCylinder) {
  1640.         count = item->getItemCount();
  1641.     }
  1642.  
  1643.     //check if we can add this item
  1644.     ReturnValue ret = toCylinder->queryAdd(index, *item, count, flags, actor);
  1645.     if (ret == RETURNVALUE_NEEDEXCHANGE) {
  1646.         //check if we can add it to source cylinder
  1647.         ret = fromCylinder->queryAdd(fromCylinder->getThingIndex(item), *toItem, toItem->getItemCount(), 0);
  1648.         if (ret == RETURNVALUE_NOERROR) {
  1649.             //check how much we can move
  1650.             uint32_t maxExchangeQueryCount = 0;
  1651.             ReturnValue retExchangeMaxCount = fromCylinder->queryMaxCount(INDEX_WHEREEVER, *toItem, toItem->getItemCount(), maxExchangeQueryCount, 0);
  1652.  
  1653.             if (retExchangeMaxCount != RETURNVALUE_NOERROR && maxExchangeQueryCount == 0) {
  1654.                 return retExchangeMaxCount;
  1655.             }
  1656.  
  1657.             if (toCylinder->queryRemove(*toItem, toItem->getItemCount(), flags, actor) == RETURNVALUE_NOERROR) {
  1658.                 int32_t oldToItemIndex = toCylinder->getThingIndex(toItem);
  1659.                 toCylinder->removeThing(toItem, toItem->getItemCount());
  1660.                 fromCylinder->addThing(toItem);
  1661.  
  1662.                 if (oldToItemIndex != -1) {
  1663.                     toCylinder->postRemoveNotification(toItem, fromCylinder, oldToItemIndex);
  1664.                 }
  1665.  
  1666.                 int32_t newToItemIndex = fromCylinder->getThingIndex(toItem);
  1667.                 if (newToItemIndex != -1) {
  1668.                     fromCylinder->postAddNotification(toItem, toCylinder, newToItemIndex);
  1669.                 }
  1670.  
  1671.                 ret = toCylinder->queryAdd(index, *item, count, flags);
  1672.                 toItem = nullptr;
  1673.             }
  1674.         }
  1675.     }
  1676.  
  1677.     if (ret != RETURNVALUE_NOERROR) {
  1678.         return ret;
  1679.     }
  1680.  
  1681.     //check how much we can move
  1682.     uint32_t maxQueryCount = 0;
  1683.     ReturnValue retMaxCount = toCylinder->queryMaxCount(index, *item, count, maxQueryCount, flags);
  1684.     if (retMaxCount != RETURNVALUE_NOERROR && maxQueryCount == 0) {
  1685.         return retMaxCount;
  1686.     }
  1687.  
  1688.     uint32_t m;
  1689.     if (item->isStackable()) {
  1690.         m = std::min<uint32_t>(count, maxQueryCount);
  1691.     } else {
  1692.         m = maxQueryCount;
  1693.     }
  1694.  
  1695.     Item* moveItem = item;
  1696.     //check if we can remove this item
  1697.     ret = fromCylinder->queryRemove(*item, m, flags, actor);
  1698.     if (ret != RETURNVALUE_NOERROR) {
  1699.         return ret;
  1700.     }
  1701.  
  1702.     if (tradeItem) {
  1703.         if (toCylinder->getItem() == tradeItem) {
  1704.             return RETURNVALUE_NOTENOUGHROOM;
  1705.         }
  1706.  
  1707.         Cylinder* tmpCylinder = toCylinder->getParent();
  1708.         while (tmpCylinder) {
  1709.             if (tmpCylinder->getItem() == tradeItem) {
  1710.                 return RETURNVALUE_NOTENOUGHROOM;
  1711.             }
  1712.  
  1713.             tmpCylinder = tmpCylinder->getParent();
  1714.         }
  1715.     }
  1716.  
  1717.     //remove the item
  1718.     int32_t itemIndex = fromCylinder->getThingIndex(item);
  1719.     Item* updateItem = nullptr;
  1720.     fromCylinder->removeThing(item, m);
  1721.  
  1722.     //update item(s)
  1723.     if (item->isStackable()) {
  1724.         uint32_t n;
  1725.         if (item->equals(toItem)) {
  1726.             n = std::min<uint32_t>(100 - toItem->getItemCount(), m);
  1727.             // Update same decay item count
  1728.             if (item->getDuration() > 0) {
  1729.                 updateItem = toItem->clone();
  1730.                 updateItem->setItemCount(toItem->getItemCount() + n);
  1731.                 toCylinder->removeThing(toItem, toItem->getItemCount());
  1732.                 toCylinder->addThing(index, updateItem);
  1733.             } else {
  1734.                 toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n);
  1735.                 updateItem = toItem;
  1736.             }
  1737.         } else {
  1738.             n = 0;
  1739.         }
  1740.  
  1741.         int32_t newCount = m - n;
  1742.         if (newCount > 0) {
  1743.             moveItem = item->clone();
  1744.             moveItem->setItemCount(newCount);
  1745.         } else {
  1746.             moveItem = nullptr;
  1747.         }
  1748.  
  1749.         if (item->isRemoved()) {
  1750.             ReleaseItem(item);
  1751.         }
  1752.     }
  1753.  
  1754.     //add item
  1755.     if (moveItem /*m - n > 0*/) {
  1756.         toCylinder->addThing(index, moveItem);
  1757.     }
  1758.  
  1759.     if (itemIndex != -1) {
  1760.         fromCylinder->postRemoveNotification(item, toCylinder, itemIndex);
  1761.     }
  1762.  
  1763.     if (moveItem) {
  1764.         int32_t moveItemIndex = toCylinder->getThingIndex(moveItem);
  1765.         if (moveItemIndex != -1) {
  1766.             toCylinder->postAddNotification(moveItem, fromCylinder, moveItemIndex);
  1767.         }
  1768.     }
  1769.  
  1770.     if (updateItem) {
  1771.         int32_t updateItemIndex = toCylinder->getThingIndex(updateItem);
  1772.         if (updateItemIndex != -1) {
  1773.             toCylinder->postAddNotification(updateItem, fromCylinder, updateItemIndex);
  1774.         }
  1775.     }
  1776.  
  1777.     if (_moveItem) {
  1778.         if (moveItem) {
  1779.             *_moveItem = moveItem;
  1780.         } else {
  1781.             *_moveItem = item;
  1782.         }
  1783.     }
  1784.  
  1785.   Item* quiver = toCylinder->getItem();
  1786.   if (quiver && quiver->getWeaponType() == WEAPON_QUIVER && quiver->getHoldingPlayer() && quiver->getHoldingPlayer()->getThing(CONST_SLOT_RIGHT) == quiver) {
  1787.     quiver->getHoldingPlayer()->sendInventoryItem(CONST_SLOT_RIGHT, quiver);
  1788.   }
  1789.   else {
  1790.     quiver = fromCylinder->getItem();
  1791.     if (quiver && quiver->getWeaponType() == WEAPON_QUIVER && quiver->getHoldingPlayer() && quiver->getHoldingPlayer()->getThing(CONST_SLOT_RIGHT) == quiver) {
  1792.       quiver->getHoldingPlayer()->sendInventoryItem(CONST_SLOT_RIGHT, quiver);
  1793.     }
  1794.   }
  1795.     //we could not move all, inform the player
  1796.     if (item->isStackable() && maxQueryCount < count) {
  1797.         return retMaxCount;
  1798.     }
  1799.  
  1800.     // looting analyser from this point forward
  1801.     if (fromCylinder && actor && toCylinder) {
  1802.         if (!fromCylinder->getContainer() || !actor->getPlayer() || !toCylinder->getContainer()) {
  1803.             return ret;
  1804.         }
  1805.  
  1806.         if (Player* player = actor->getPlayer()) {
  1807.             const ItemType& it = Item::items[fromCylinder->getItem()->getID()];
  1808.             if (it.id <= 0) {
  1809.                 return ret;
  1810.             }
  1811.  
  1812.             if (it.corpseType != RACE_NONE && toCylinder->getContainer()->getTopParent() == player && item->getIsLootTrackeable()) {
  1813.                 player->updateLootTracker(item);
  1814.             }
  1815.         }
  1816.     }
  1817.  
  1818.     if (moveItem && moveItem->getDuration() > 0) {
  1819.         if (moveItem->getDecaying() != DECAYING_TRUE) {
  1820.             moveItem->incrementReferenceCounter();
  1821.             moveItem->setDecaying(DECAYING_TRUE);
  1822.             g_game.toDecayItems.push_front(moveItem);
  1823.         }
  1824.     }
  1825.  
  1826.     return ret;
  1827. }
  1828.  
  1829. ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index /*= INDEX_WHEREEVER*/,
  1830.                                   uint32_t flags/* = 0*/, bool test/* = false*/)
  1831. {
  1832.     uint32_t remainderCount = 0;
  1833.     return internalAddItem(toCylinder, item, index, flags, test, remainderCount);
  1834. }
  1835.  
  1836. ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index,
  1837.                                   uint32_t flags, bool test, uint32_t& remainderCount)
  1838. {
  1839.     if (toCylinder == nullptr || item == nullptr) {
  1840.         return RETURNVALUE_NOTPOSSIBLE;
  1841.     }
  1842.  
  1843.     Cylinder* destCylinder = toCylinder;
  1844.     Item* toItem = nullptr;
  1845.     toCylinder = toCylinder->queryDestination(index, *item, &toItem, flags);
  1846.  
  1847.     //check if we can add this item
  1848.     ReturnValue ret = toCylinder->queryAdd(index, *item, item->getItemCount(), flags);
  1849.     if (ret != RETURNVALUE_NOERROR) {
  1850.         return ret;
  1851.     }
  1852.  
  1853.     /*
  1854.     Check if we can move add the whole amount, we do this by checking against the original cylinder,
  1855.     since the queryDestination can return a cylinder that might only hold a part of the full amount.
  1856.     */
  1857.     uint32_t maxQueryCount = 0;
  1858.     ret = destCylinder->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), maxQueryCount, flags);
  1859.  
  1860.     if (ret != RETURNVALUE_NOERROR && toCylinder->getItem() && toCylinder->getItem()->getID() != ITEM_REWARD_CONTAINER) {
  1861.         return ret;
  1862.     }
  1863.  
  1864.     if (test) {
  1865.         return RETURNVALUE_NOERROR;
  1866.     }
  1867.  
  1868.     if (item->isStackable() && item->equals(toItem)) {
  1869.         uint32_t m = std::min<uint32_t>(item->getItemCount(), maxQueryCount);
  1870.         uint32_t n = std::min<uint32_t>(100 - toItem->getItemCount(), m);
  1871.  
  1872.         toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n);
  1873.  
  1874.         int32_t count = m - n;
  1875.         if (count > 0) {
  1876.             if (item->getItemCount() != count) {
  1877.                 Item* remainderItem = item->clone();
  1878.                 remainderItem->setItemCount(count);
  1879.                 if (internalAddItem(destCylinder, remainderItem, INDEX_WHEREEVER, flags, false) != RETURNVALUE_NOERROR) {
  1880.                     ReleaseItem(remainderItem);
  1881.                     remainderCount = count;
  1882.                 }
  1883.             } else {
  1884.                 toCylinder->addThing(index, item);
  1885.  
  1886.                 int32_t itemIndex = toCylinder->getThingIndex(item);
  1887.                 if (itemIndex != -1) {
  1888.                     toCylinder->postAddNotification(item, nullptr, itemIndex);
  1889.                 }
  1890.             }
  1891.         } else {
  1892.             //fully merged with toItem, item will be destroyed
  1893.             item->onRemoved();
  1894.             ReleaseItem(item);
  1895.  
  1896.             int32_t itemIndex = toCylinder->getThingIndex(toItem);
  1897.             if (itemIndex != -1) {
  1898.                 toCylinder->postAddNotification(toItem, nullptr, itemIndex);
  1899.             }
  1900.         }
  1901.     } else {
  1902.         toCylinder->addThing(index, item);
  1903.  
  1904.         int32_t itemIndex = toCylinder->getThingIndex(item);
  1905.         if (itemIndex != -1) {
  1906.             toCylinder->postAddNotification(item, nullptr, itemIndex);
  1907.         }
  1908.     }
  1909.  
  1910.     if (item->getDuration() > 0) {
  1911.         item->incrementReferenceCounter();
  1912.         item->setDecaying(DECAYING_TRUE);
  1913.         g_game.toDecayItems.push_front(item);
  1914.     }
  1915.  
  1916.   Item* quiver = toCylinder->getItem();
  1917.   if (quiver && quiver->getWeaponType() == WEAPON_QUIVER && quiver->getHoldingPlayer() && quiver->getHoldingPlayer()->getThing(CONST_SLOT_RIGHT) == quiver) {
  1918.     quiver->getHoldingPlayer()->sendInventoryItem(CONST_SLOT_RIGHT, quiver);
  1919.   }
  1920.     return RETURNVALUE_NOERROR;
  1921. }
  1922.  
  1923. ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool test /*= false*/, uint32_t flags /*= 0*/)
  1924. {
  1925.     Cylinder* cylinder = item->getParent();
  1926.     if (cylinder == nullptr) {
  1927.         return RETURNVALUE_NOTPOSSIBLE;
  1928.     }
  1929.     Tile* fromTile = cylinder->getTile();
  1930.     if (fromTile) {
  1931.         auto it = browseFields.find(fromTile);
  1932.         if (it != browseFields.end() && it->second == cylinder) {
  1933.             cylinder = fromTile;
  1934.         }
  1935.     }
  1936.     if (count == -1) {
  1937.         count = item->getItemCount();
  1938.     }
  1939.     //check if we can remove this item
  1940.     ReturnValue ret = cylinder->queryRemove(*item, count, flags | FLAG_IGNORENOTMOVEABLE);
  1941.     if (ret != RETURNVALUE_NOERROR) {
  1942.         return ret;
  1943.     }
  1944.     if (!item->canRemove()) {
  1945.         return RETURNVALUE_NOTPOSSIBLE;
  1946.     }
  1947.     if (!test) {
  1948.         int32_t index = cylinder->getThingIndex(item);
  1949.         //remove the item
  1950.         cylinder->removeThing(item, count);
  1951.  
  1952.         if (item->isRemoved()) {
  1953.             item->onRemoved();
  1954.             if (item->canDecay()) {
  1955.                 decayItems->remove(item);
  1956.             }
  1957.             ReleaseItem(item);
  1958.         }
  1959.  
  1960.         cylinder->postRemoveNotification(item, nullptr, index);
  1961.     }
  1962.   Item* quiver = cylinder->getItem();
  1963.   if (quiver && quiver->getWeaponType() == WEAPON_QUIVER && quiver->getHoldingPlayer() && quiver->getHoldingPlayer()->getThing(CONST_SLOT_RIGHT) == quiver) {
  1964.     quiver->getHoldingPlayer()->sendInventoryItem(CONST_SLOT_RIGHT, quiver);
  1965.   }
  1966.     return RETURNVALUE_NOERROR;
  1967. }
  1968.  
  1969. ReturnValue Game::internalPlayerAddItem(Player* player, Item* item, bool dropOnMap /*= true*/, slots_t slot /*= CONST_SLOT_WHEREEVER*/)
  1970. {
  1971.     uint32_t remainderCount = 0;
  1972.     ReturnValue ret = internalAddItem(player, item, static_cast<int32_t>(slot), 0, false, remainderCount);
  1973.     if (remainderCount != 0) {
  1974.         Item* remainderItem = Item::CreateItem(item->getID(), remainderCount);
  1975.         ReturnValue remaindRet = internalAddItem(player->getTile(), remainderItem, INDEX_WHEREEVER, FLAG_NOLIMIT);
  1976.         if (remaindRet != RETURNVALUE_NOERROR) {
  1977.             ReleaseItem(remainderItem);
  1978.             player->updateLootTracker(item);
  1979.         }
  1980.     }
  1981.  
  1982.     if (ret != RETURNVALUE_NOERROR && dropOnMap) {
  1983.         ret = internalAddItem(player->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT);
  1984.     }
  1985.  
  1986.     return ret;
  1987. }
  1988.  
  1989. Item* Game::findItemOfType(Cylinder* cylinder, uint16_t itemId,
  1990.                            bool depthSearch /*= true*/, int32_t subType /*= -1*/) const
  1991. {
  1992.     if (cylinder == nullptr) {
  1993.         return nullptr;
  1994.     }
  1995.  
  1996.     std::vector<Container*> containers;
  1997.     for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex(); i < j; ++i) {
  1998.         Thing* thing = cylinder->getThing(i);
  1999.         if (!thing) {
  2000.             continue;
  2001.         }
  2002.  
  2003.         Item* item = thing->getItem();
  2004.         if (!item) {
  2005.             continue;
  2006.         }
  2007.  
  2008.         if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) {
  2009.             return item;
  2010.         }
  2011.  
  2012.         if (depthSearch) {
  2013.             Container* container = item->getContainer();
  2014.             if (container) {
  2015.                 containers.push_back(container);
  2016.             }
  2017.         }
  2018.     }
  2019.  
  2020.     size_t i = 0;
  2021.     while (i < containers.size()) {
  2022.         Container* container = containers[i++];
  2023.         for (Item* item : container->getItemList()) {
  2024.             if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) {
  2025.                 return item;
  2026.             }
  2027.  
  2028.             Container* subContainer = item->getContainer();
  2029.             if (subContainer) {
  2030.                 containers.push_back(subContainer);
  2031.             }
  2032.         }
  2033.     }
  2034.     return nullptr;
  2035. }
  2036.  
  2037. bool Game::removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/, bool useBalance /*= false*/)
  2038. {
  2039.     if (cylinder == nullptr) {
  2040.         return false;
  2041.     }
  2042.     if (money == 0) {
  2043.         return true;
  2044.     }
  2045.     std::vector<Container*> containers;
  2046.     std::multimap<uint32_t, Item*> moneyMap;
  2047.     uint64_t moneyCount = 0;
  2048.     for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex(); i < j; ++i) {
  2049.         Thing* thing = cylinder->getThing(i);
  2050.         if (!thing) {
  2051.             continue;
  2052.         }
  2053.         Item* item = thing->getItem();
  2054.         if (!item) {
  2055.             continue;
  2056.         }
  2057.         Container* container = item->getContainer();
  2058.         if (container) {
  2059.             containers.push_back(container);
  2060.         } else {
  2061.             const uint32_t worth = item->getWorth();
  2062.             if (worth != 0) {
  2063.                 moneyCount += worth;
  2064.                 moneyMap.emplace(worth, item);
  2065.             }
  2066.         }
  2067.     }
  2068.     size_t i = 0;
  2069.     while (i < containers.size()) {
  2070.         Container* container = containers[i++];
  2071.         for (Item* item : container->getItemList()) {
  2072.             Container* tmpContainer = item->getContainer();
  2073.             if (tmpContainer) {
  2074.                 containers.push_back(tmpContainer);
  2075.             } else {
  2076.                 const uint32_t worth = item->getWorth();
  2077.                 if (worth != 0) {
  2078.                     moneyCount += worth;
  2079.                     moneyMap.emplace(worth, item);
  2080.                 }
  2081.             }
  2082.         }
  2083.     }
  2084.  
  2085.     Player* player = useBalance ? dynamic_cast<Player*>(cylinder) : nullptr;
  2086.     uint64_t balance = 0;
  2087.     if (useBalance && player) {
  2088.         balance = player->getBankBalance();
  2089.     }
  2090.  
  2091.     if (moneyCount + balance < money) {
  2092.         return false;
  2093.     }
  2094.  
  2095.     for (const auto& moneyEntry : moneyMap) {
  2096.         Item* item = moneyEntry.second;
  2097.         if (moneyEntry.first < money) {
  2098.             internalRemoveItem(item);
  2099.             money -= moneyEntry.first;
  2100.         } else if (moneyEntry.first > money) {
  2101.             const uint32_t worth = moneyEntry.first / item->getItemCount();
  2102.             const uint32_t removeCount = std::ceil(money / static_cast<double>(worth));
  2103.             addMoney(cylinder, (worth * removeCount) - money, flags);
  2104.             internalRemoveItem(item, removeCount);
  2105.             break;
  2106.         } else {
  2107.             internalRemoveItem(item);
  2108.             break;
  2109.         }
  2110.     }
  2111.  
  2112.     if (useBalance && player && player->getBankBalance() >= money) {
  2113.         player->setBankBalance(player->getBankBalance() - money);
  2114.     }
  2115.  
  2116.     return true;
  2117. }
  2118.  
  2119. void Game::addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/)
  2120. {
  2121.     if (money == 0) {
  2122.         return;
  2123.     }
  2124.  
  2125.     uint32_t crystalCoins = money / 10000;
  2126.     money -= crystalCoins * 10000;
  2127.     while (crystalCoins > 0) {
  2128.         const uint16_t count = std::min<uint32_t>(100, crystalCoins);
  2129.  
  2130.         Item* remaindItem = Item::CreateItem(ITEM_CRYSTAL_COIN, count);
  2131.  
  2132.         ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags);
  2133.         if (ret != RETURNVALUE_NOERROR) {
  2134.             internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT);
  2135.         }
  2136.  
  2137.         crystalCoins -= count;
  2138.     }
  2139.  
  2140.     uint16_t platinumCoins = money / 100;
  2141.     if (platinumCoins != 0) {
  2142.         Item* remaindItem = Item::CreateItem(ITEM_PLATINUM_COIN, platinumCoins);
  2143.  
  2144.         ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags);
  2145.         if (ret != RETURNVALUE_NOERROR) {
  2146.             internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT);
  2147.         }
  2148.  
  2149.         money -= platinumCoins * 100;
  2150.     }
  2151.  
  2152.     if (money != 0) {
  2153.         Item* remaindItem = Item::CreateItem(ITEM_GOLD_COIN, money);
  2154.  
  2155.         ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags);
  2156.         if (ret != RETURNVALUE_NOERROR) {
  2157.             internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT);
  2158.         }
  2159.     }
  2160. }
  2161.  
  2162. Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/)
  2163. {
  2164.     if (item->getID() == newId && (newCount == -1 || (newCount == item->getSubType() && newCount != 0))) { //chargeless item placed on map = infinite
  2165.         return item;
  2166.     }
  2167.  
  2168.     Cylinder* cylinder = item->getParent();
  2169.     if (cylinder == nullptr) {
  2170.         return nullptr;
  2171.     }
  2172.  
  2173.     Tile* fromTile = cylinder->getTile();
  2174.     if (fromTile) {
  2175.         auto it = browseFields.find(fromTile);
  2176.         if (it != browseFields.end() && it->second == cylinder) {
  2177.             cylinder = fromTile;
  2178.         }
  2179.     }
  2180.  
  2181.     int32_t itemIndex = cylinder->getThingIndex(item);
  2182.     if (itemIndex == -1) {
  2183.         return item;
  2184.     }
  2185.  
  2186.     if (!item->canTransform()) {
  2187.         return item;
  2188.     }
  2189.  
  2190.     const ItemType& newType = Item::items[newId];
  2191.     if (newType.id == 0) {
  2192.         return item;
  2193.     }
  2194.  
  2195.     const ItemType& curType = Item::items[item->getID()];
  2196.     if (curType.alwaysOnTop != newType.alwaysOnTop) {
  2197.         //This only occurs when you transform items on tiles from a downItem to a topItem (or vice versa)
  2198.         //Remove the old, and add the new
  2199.         cylinder->removeThing(item, item->getItemCount());
  2200.         cylinder->postRemoveNotification(item, cylinder, itemIndex);
  2201.  
  2202.         item->setID(newId);
  2203.         if (newCount != -1) {
  2204.             item->setSubType(newCount);
  2205.         }
  2206.         cylinder->addThing(item);
  2207.  
  2208.         Cylinder* newParent = item->getParent();
  2209.         if (newParent == nullptr) {
  2210.             ReleaseItem(item);
  2211.             return nullptr;
  2212.         }
  2213.  
  2214.         newParent->postAddNotification(item, cylinder, newParent->getThingIndex(item));
  2215.         return item;
  2216.     }
  2217.  
  2218.     if (curType.type == newType.type) {
  2219.         //Both items has the same type so we can safely change id/subtype
  2220.         if (newCount == 0 && (item->isStackable() || item->hasAttribute(ITEM_ATTRIBUTE_CHARGES))) {
  2221.             if (item->isStackable()) {
  2222.                 internalRemoveItem(item);
  2223.                 return nullptr;
  2224.             } else {
  2225.                 int32_t newItemId = newId;
  2226.                 if (curType.id == newType.id) {
  2227.                     newItemId = curType.decayTo;
  2228.                 }
  2229.  
  2230.                 if (newItemId <= 0) {
  2231.                     internalRemoveItem(item);
  2232.                     return nullptr;
  2233.                 } else if (newItemId != newId) {
  2234.                     //Replacing the the old item with the new while maintaining the old position
  2235.                     Item* newItem = Item::CreateItem(newItemId, 1);
  2236.                     if (newItem == nullptr) {
  2237.                         return nullptr;
  2238.                     }
  2239.  
  2240.                     cylinder->replaceThing(itemIndex, newItem);
  2241.                     cylinder->postAddNotification(newItem, cylinder, itemIndex);
  2242.  
  2243.                     item->setParent(nullptr);
  2244.                     cylinder->postRemoveNotification(item, cylinder, itemIndex);
  2245.                     ReleaseItem(item);
  2246.                     return newItem;
  2247.                 } else {
  2248.                     return transformItem(item, newItemId);
  2249.                 }
  2250.             }
  2251.         } else {
  2252.             uint32_t currentDuration = item->getDuration();
  2253.  
  2254.             cylinder->postRemoveNotification(item, cylinder, itemIndex);
  2255.             uint16_t itemId = item->getID();
  2256.             int32_t count = item->getSubType();
  2257.  
  2258.             if (curType.id != newType.id) {
  2259.                 if (newType.group != curType.group) {
  2260.                     item->setDefaultSubtype();
  2261.                 }
  2262.  
  2263.                 itemId = newId;
  2264.             }
  2265.  
  2266.             if (newCount != -1 && newType.hasSubType()) {
  2267.                 count = newCount;
  2268.             }
  2269.  
  2270.             cylinder->updateThing(item, itemId, count);
  2271.             if (currentDuration) {
  2272.                 item->setDuration(currentDuration);
  2273.             }
  2274.             cylinder->postAddNotification(item, cylinder, itemIndex);
  2275.       Item* quiver = cylinder->getItem();
  2276.       if (quiver && quiver->getWeaponType() == WEAPON_QUIVER && quiver->getHoldingPlayer() && quiver->getHoldingPlayer()->getThing(CONST_SLOT_RIGHT) == quiver) {
  2277.         quiver->getHoldingPlayer()->sendInventoryItem(CONST_SLOT_RIGHT, quiver);
  2278.       }
  2279.             return item;
  2280.         }
  2281.     }
  2282.  
  2283.     //Replacing the the old item with the new while maintaining the old position
  2284.     Item* newItem;
  2285.     if (newCount == -1) {
  2286.         newItem = Item::CreateItem(newId);
  2287.     } else {
  2288.         newItem = Item::CreateItem(newId, newCount);
  2289.     }
  2290.  
  2291.     if (newItem == nullptr) {
  2292.         return nullptr;
  2293.     }
  2294.  
  2295.     cylinder->replaceThing(itemIndex, newItem);
  2296.     cylinder->postAddNotification(newItem, cylinder, itemIndex);
  2297.  
  2298.     item->setParent(nullptr);
  2299.     cylinder->postRemoveNotification(item, cylinder, itemIndex);
  2300.     ReleaseItem(item);
  2301.  
  2302.     if (newItem->getDuration() > 0) {
  2303.         if (newItem->getDecaying() != DECAYING_TRUE) {
  2304.             newItem->incrementReferenceCounter();
  2305.             newItem->setDecaying(DECAYING_TRUE);
  2306.             g_game.toDecayItems.push_front(newItem);
  2307.         }
  2308.     }
  2309.  
  2310.   Item* quiver = cylinder->getItem();
  2311.   if (quiver && quiver->getWeaponType() == WEAPON_QUIVER && quiver->getHoldingPlayer() && quiver->getHoldingPlayer()->getThing(CONST_SLOT_RIGHT) == quiver) {
  2312.     quiver->getHoldingPlayer()->sendInventoryItem(CONST_SLOT_RIGHT, quiver);
  2313.   }
  2314.     return newItem;
  2315. }
  2316.  
  2317. ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pushMove/* = true*/, uint32_t flags /*= 0*/)
  2318. {
  2319.     if (newPos == thing->getPosition()) {
  2320.         return RETURNVALUE_NOERROR;
  2321.     } else if (thing->isRemoved()) {
  2322.         return RETURNVALUE_NOTPOSSIBLE;
  2323.     }
  2324.  
  2325.     Tile* toTile = map.getTile(newPos);
  2326.     if (!toTile) {
  2327.         return RETURNVALUE_NOTPOSSIBLE;
  2328.     }
  2329.  
  2330.     if (Creature* creature = thing->getCreature()) {
  2331.         ReturnValue ret = toTile->queryAdd(0, *creature, 1, FLAG_NOLIMIT);
  2332.         if (ret != RETURNVALUE_NOERROR) {
  2333.             return ret;
  2334.         }
  2335.  
  2336.         map.moveCreature(*creature, *toTile, !pushMove);
  2337.         return RETURNVALUE_NOERROR;
  2338.     } else if (Item* item = thing->getItem()) {
  2339.         return internalMoveItem(item->getParent(), toTile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr, flags);
  2340.     }
  2341.     return RETURNVALUE_NOTPOSSIBLE;
  2342. }
  2343.  
  2344. void Game::internalQuickLootCorpse(Player* player, Container* corpse)
  2345. {
  2346.     if (!player || !corpse) {
  2347.         return;
  2348.     }
  2349.  
  2350.     std::vector<Item*> itemList;
  2351.     bool ignoreListItems = (player->quickLootFilter == QUICKLOOTFILTER_SKIPPEDLOOT);
  2352.  
  2353.     bool missedAnyGold = false;
  2354.     bool missedAnyItem = false;
  2355.  
  2356.     for (ContainerIterator it = corpse->iterator(); it.hasNext(); it.advance()) {
  2357.         Item* item = *it;
  2358.         bool listed = player->isQuickLootListedItem(item);
  2359.         if ((listed && ignoreListItems) || (!listed && !ignoreListItems)) {
  2360.             if (item->getWorth() != 0) {
  2361.                 missedAnyGold = true;
  2362.             } else {
  2363.                 missedAnyItem = true;
  2364.             }
  2365.             continue;
  2366.         }
  2367.  
  2368.         itemList.push_back(item);
  2369.     }
  2370.  
  2371.     bool shouldNotifyCapacity = false;
  2372.     ObjectCategory_t shouldNotifyNotEnoughRoom = OBJECTCATEGORY_NONE;
  2373.  
  2374.     uint32_t totalLootedGold = 0;
  2375.     uint32_t totalLootedItems = 0;
  2376.     for (Item* item : itemList) {
  2377.         uint32_t worth = item->getWorth();
  2378.         uint16_t baseCount = item->getItemCount();
  2379.         ObjectCategory_t category = getObjectCategory(item);
  2380.  
  2381.         ReturnValue ret = internalQuickLootItem(player, item, category);
  2382.         if (ret == RETURNVALUE_NOTENOUGHCAPACITY) {
  2383.             shouldNotifyCapacity = true;
  2384.         } else if (ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) {
  2385.             shouldNotifyNotEnoughRoom = category;
  2386.         }
  2387.  
  2388.         bool success = ret == RETURNVALUE_NOERROR;
  2389.         if (worth != 0) {
  2390.             missedAnyGold = missedAnyGold || !success;
  2391.             if (success) {
  2392.                 player->sendLootStats(item, baseCount);
  2393.                 totalLootedGold += worth;
  2394.             } else {
  2395.                 // item is not completely moved
  2396.                 totalLootedGold += worth - item->getWorth();
  2397.             }
  2398.         } else {
  2399.             missedAnyItem = missedAnyItem || !success;
  2400.             if (success || item->getItemCount() != baseCount) {
  2401.                 totalLootedItems++;
  2402.                 player->sendLootStats(item, item->getItemCount());
  2403.             }
  2404.         }
  2405.     }
  2406.  
  2407.     std::stringstream ss;
  2408.     if (totalLootedGold != 0 || missedAnyGold || totalLootedItems != 0 || missedAnyItem) {
  2409.         bool lootedAllGold = totalLootedGold != 0 && !missedAnyGold;
  2410.         bool lootedAllItems = totalLootedItems != 0 && !missedAnyItem;
  2411.         if (lootedAllGold) {
  2412.             if (totalLootedItems != 0 || missedAnyItem) {
  2413.                 ss << "You looted the complete " << totalLootedGold << " gold";
  2414.  
  2415.                 if (lootedAllItems) {
  2416.                     ss << " and all dropped items";
  2417.                 } else if (totalLootedItems != 0) {
  2418.                     ss << ", but you only looted some of the items";
  2419.                 } else if (missedAnyItem) {
  2420.                     ss << " but none of the dropped items";
  2421.                 }
  2422.             } else {
  2423.                 ss << "You looted " << totalLootedGold << " gold";
  2424.             }
  2425.         } else if (lootedAllItems) {
  2426.             if (totalLootedItems == 1) {
  2427.                 ss << "You looted 1 item";
  2428.             } else if (totalLootedGold != 0 || missedAnyGold) {
  2429.                 ss << "You looted all of the dropped items";
  2430.             } else {
  2431.                 ss << "You looted all items";
  2432.             }
  2433.  
  2434.             if (totalLootedGold != 0) {
  2435.                 ss << ", but you only looted " << totalLootedGold << " of the dropped gold";
  2436.             } else if (missedAnyGold) {
  2437.                 ss << " but none of the dropped gold";
  2438.             }
  2439.         } else if (totalLootedGold != 0) {
  2440.             ss << "You only looted " << totalLootedGold << " of the dropped gold";
  2441.             if (totalLootedItems != 0) {
  2442.                 ss << " and some of the dropped items";
  2443.             } else if (missedAnyItem) {
  2444.                 ss << " but none of the dropped items";
  2445.             }
  2446.         } else if (totalLootedItems != 0) {
  2447.             ss << "You looted some of the dropped items";
  2448.             if (missedAnyGold) {
  2449.                 ss << " but none of the dropped gold";
  2450.             }
  2451.         } else if (missedAnyGold) {
  2452.             ss << "You looted none of the dropped gold";
  2453.             if (missedAnyItem) {
  2454.                 ss << " and none of the items";
  2455.             }
  2456.         } else if (missedAnyItem) {
  2457.             ss << "You looted none of the dropped items";
  2458.         }
  2459.     } else {
  2460.         ss << "No loot";
  2461.     }
  2462.  
  2463.     ss << ".";
  2464.     player->sendTextMessage(MESSAGE_LOOT, ss.str());
  2465.  
  2466.     if (shouldNotifyCapacity) {
  2467.         ss.str(std::string());
  2468.         ss << "Attention! The loot you are trying to pick up is too heavy for you to carry.";
  2469.     } else if (shouldNotifyNotEnoughRoom != OBJECTCATEGORY_NONE) {
  2470.         ss.str(std::string());
  2471.         ss << "Attention! The container assigned to category " << getObjectCategoryName(shouldNotifyNotEnoughRoom) << " is full.";
  2472.     } else {
  2473.         return;
  2474.     }
  2475.  
  2476.     if (player->lastQuickLootNotification + 15000 < OTSYS_TIME()) {
  2477.         player->sendTextMessage(MESSAGE_GAME_HIGHLIGHT, ss.str());
  2478.     } else {
  2479.         player->sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str());
  2480.     }
  2481.  
  2482.     player->lastQuickLootNotification = OTSYS_TIME();
  2483. }
  2484.  
  2485. ReturnValue Game::internalQuickLootItem(Player* player, Item* item, ObjectCategory_t category /* = OBJECTCATEGORY_DEFAULT*/)
  2486. {
  2487.   if (!player || !item) {
  2488.     return RETURNVALUE_NOTPOSSIBLE;
  2489.   }
  2490.  
  2491.     bool fallbackConsumed = false;
  2492.     uint16_t baseId = 0;
  2493.  
  2494.     Container* lootContainer = player->getLootContainer(category);
  2495.     if (!lootContainer) {
  2496.         if (player->quickLootFallbackToMainContainer) {
  2497.             Item* fallbackItem = player->getInventoryItem(CONST_SLOT_BACKPACK);
  2498.  
  2499.         if (fallbackItem) {
  2500.             Container* mainBackpack = fallbackItem->getContainer();
  2501.             if (mainBackpack && !fallbackConsumed) {
  2502.                 player->setLootContainer(OBJECTCATEGORY_DEFAULT, mainBackpack);
  2503.                 player->sendInventoryItem(CONST_SLOT_BACKPACK, player->getInventoryItem(CONST_SLOT_BACKPACK));
  2504.             }
  2505.         }
  2506.  
  2507.             lootContainer = fallbackItem ? fallbackItem->getContainer() : nullptr;
  2508.             fallbackConsumed = true;
  2509.         } else {
  2510.             return RETURNVALUE_NOTPOSSIBLE;
  2511.         }
  2512.     } else {
  2513.         baseId = lootContainer->getID();
  2514.     }
  2515.  
  2516.     if (!lootContainer) {
  2517.         return RETURNVALUE_NOTPOSSIBLE;
  2518.     }
  2519.  
  2520.     Container* lastSubContainer = nullptr;
  2521.     uint32_t remainderCount = item->getItemCount();
  2522.     ContainerIterator it = lootContainer->iterator();
  2523.  
  2524.     ReturnValue ret;
  2525.     do {
  2526.         Item* moveItem = nullptr;
  2527.         if (item->getParent()) { // Stash retrive dont have parent cylinder.
  2528.         ret = internalMoveItem(item->getParent(), lootContainer, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, 0, player);
  2529.         } else {
  2530.         ret = internalAddItem(lootContainer, item, INDEX_WHEREEVER);
  2531.         }
  2532.         if (moveItem) {
  2533.             remainderCount -= moveItem->getItemCount();
  2534.         }
  2535.  
  2536.         if (ret != RETURNVALUE_CONTAINERNOTENOUGHROOM) {
  2537.             break;
  2538.         }
  2539.  
  2540.         // search for a sub container
  2541.         bool obtainedNewContainer = false;
  2542.         while (it.hasNext()) {
  2543.             Item* cur = *it;
  2544.             Container* subContainer = cur ? cur->getContainer() : nullptr;
  2545.             it.advance();
  2546.  
  2547.             if (subContainer) {
  2548.                 lastSubContainer = subContainer;
  2549.                 lootContainer = subContainer;
  2550.                 obtainedNewContainer = true;
  2551.                 break;
  2552.             }
  2553.         }
  2554.  
  2555.         // a hack to fix last empty sub-container
  2556.         if (!obtainedNewContainer && lastSubContainer && lastSubContainer->size() > 0) {
  2557.             Item* cur = lastSubContainer->getItemByIndex(lastSubContainer->size() - 1);
  2558.             Container* subContainer = cur ? cur->getContainer() : nullptr;
  2559.  
  2560.             if (subContainer) {
  2561.                 lootContainer = subContainer;
  2562.                 obtainedNewContainer = true;
  2563.             }
  2564.  
  2565.             lastSubContainer = nullptr;
  2566.         }
  2567.  
  2568.         // consumed all sub-container & there is simply no more containers to iterate over.
  2569.         // check if fallback should be used and if not, then break
  2570.         bool quickFallback = (player->quickLootFallbackToMainContainer);
  2571.         bool noFallback = fallbackConsumed || !quickFallback;
  2572.         if (noFallback && (!lootContainer || !obtainedNewContainer)) {
  2573.             break;
  2574.         } else if (!lootContainer || !obtainedNewContainer) {
  2575.             Item* fallbackItem = player->getInventoryItem(CONST_SLOT_BACKPACK);
  2576.             if (!fallbackItem || !fallbackItem->getContainer()) {
  2577.                 break;
  2578.             }
  2579.  
  2580.             lootContainer = fallbackItem->getContainer();
  2581.             it = lootContainer->iterator();
  2582.  
  2583.             fallbackConsumed = true;
  2584.         }
  2585.     } while (remainderCount != 0);
  2586.     return ret;
  2587. }
  2588.  
  2589. ObjectCategory_t Game::getObjectCategory(const Item* item)
  2590. {
  2591.     ObjectCategory_t category = OBJECTCATEGORY_DEFAULT;
  2592.   if (!item) {
  2593.     return OBJECTCATEGORY_NONE;
  2594.   }
  2595.  
  2596.     const ItemType& it = Item::items[item->getID()];
  2597.     if (item->getWorth() != 0) {
  2598.         category = OBJECTCATEGORY_GOLD;
  2599.     } else if (it.weaponType != WEAPON_NONE) {
  2600.         switch (it.weaponType) {
  2601.             case WEAPON_SWORD:
  2602.                 category = OBJECTCATEGORY_SWORDS;
  2603.                 break;
  2604.             case WEAPON_CLUB:
  2605.                 category = OBJECTCATEGORY_CLUBS;
  2606.                 break;
  2607.             case WEAPON_AXE:
  2608.                 category = OBJECTCATEGORY_AXES;
  2609.                 break;
  2610.             case WEAPON_SHIELD:
  2611.                 category = OBJECTCATEGORY_SHIELDS;
  2612.                 break;
  2613.             case WEAPON_DISTANCE:
  2614.                 category = OBJECTCATEGORY_DISTANCEWEAPONS;
  2615.                 break;
  2616.             case WEAPON_WAND:
  2617.                 category = OBJECTCATEGORY_WANDS;
  2618.                 break;
  2619.             case WEAPON_AMMO:
  2620.                 category = OBJECTCATEGORY_AMMO;
  2621.                 break;
  2622.             default:
  2623.                 break;
  2624.         }
  2625.     } else if (it.slotPosition != SLOTP_HAND) { // if it's a weapon/shield should have been parsed earlier
  2626.         if ((it.slotPosition & SLOTP_HEAD) != 0) {
  2627.             category = OBJECTCATEGORY_HELMETS;
  2628.         } else if ((it.slotPosition & SLOTP_NECKLACE) != 0) {
  2629.             category = OBJECTCATEGORY_NECKLACES;
  2630.         } else if ((it.slotPosition & SLOTP_BACKPACK) != 0) {
  2631.             category = OBJECTCATEGORY_CONTAINERS;
  2632.         } else if ((it.slotPosition & SLOTP_ARMOR) != 0) {
  2633.             category = OBJECTCATEGORY_ARMORS;
  2634.         } else if ((it.slotPosition & SLOTP_LEGS) != 0) {
  2635.             category = OBJECTCATEGORY_LEGS;
  2636.         } else if ((it.slotPosition & SLOTP_FEET) != 0) {
  2637.             category = OBJECTCATEGORY_BOOTS;
  2638.         } else if ((it.slotPosition & SLOTP_RING) != 0) {
  2639.             category = OBJECTCATEGORY_RINGS;
  2640.         }
  2641.     } else if (it.type == ITEM_TYPE_RUNE) {
  2642.         category = OBJECTCATEGORY_RUNES;
  2643.     } else if (it.type == ITEM_TYPE_CREATUREPRODUCT) {
  2644.         category = OBJECTCATEGORY_CREATUREPRODUCTS;
  2645.     } else if (it.type == ITEM_TYPE_FOOD) {
  2646.         category = OBJECTCATEGORY_FOOD;
  2647.     } else if (it.type == ITEM_TYPE_VALUABLE) {
  2648.         category = OBJECTCATEGORY_VALUABLES;
  2649.     } else if (it.type == ITEM_TYPE_POTION) {
  2650.         category = OBJECTCATEGORY_POTIONS;
  2651.     } else {
  2652.         category = OBJECTCATEGORY_OTHERS;
  2653.     }
  2654.  
  2655.     return category;
  2656. }
  2657.  
  2658. Item* searchForItem(Container* container, uint16_t itemId)
  2659. {
  2660.     for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) {
  2661.         if ((*it)->getID() == itemId) {
  2662.             return *it;
  2663.         }
  2664.     }
  2665.  
  2666.     return nullptr;
  2667. }
  2668.  
  2669. slots_t getSlotType(const ItemType& it)
  2670. {
  2671.     slots_t slot = CONST_SLOT_RIGHT;
  2672.     if (it.weaponType != WeaponType_t::WEAPON_SHIELD) {
  2673.         int32_t slotPosition = it.slotPosition;
  2674.  
  2675.         if (slotPosition & SLOTP_HEAD) {
  2676.             slot = CONST_SLOT_HEAD;
  2677.         } else if (slotPosition & SLOTP_NECKLACE) {
  2678.             slot = CONST_SLOT_NECKLACE;
  2679.         } else if (slotPosition & SLOTP_ARMOR) {
  2680.             slot = CONST_SLOT_ARMOR;
  2681.         } else if (slotPosition & SLOTP_LEGS) {
  2682.             slot = CONST_SLOT_LEGS;
  2683.         } else if (slotPosition & SLOTP_FEET) {
  2684.             slot = CONST_SLOT_FEET ;
  2685.         } else if (slotPosition & SLOTP_RING) {
  2686.             slot = CONST_SLOT_RING;
  2687.         } else if (slotPosition & SLOTP_AMMO) {
  2688.             slot = CONST_SLOT_AMMO;
  2689.         } else if (slotPosition & SLOTP_TWO_HAND || slotPosition & SLOTP_LEFT) {
  2690.             slot = CONST_SLOT_LEFT;
  2691.         }
  2692.     }
  2693.  
  2694.     return slot;
  2695. }
  2696.  
  2697. //Implementation of player invoked events
  2698. void Game::playerEquipItem(uint32_t playerId, uint16_t spriteId)
  2699. {
  2700.     Player* player = getPlayerByID(playerId);
  2701.     if (!player) {
  2702.         return;
  2703.     }
  2704.  
  2705.     Item* item = player->getInventoryItem(CONST_SLOT_BACKPACK);
  2706.     if (!item) {
  2707.         return;
  2708.     }
  2709.  
  2710.     Container* backpack = item->getContainer();
  2711.     if (!backpack) {
  2712.         return;
  2713.     }
  2714.  
  2715.     const ItemType& it = Item::items.getItemIdByClientId(spriteId);
  2716.     slots_t slot = getSlotType(it);
  2717.  
  2718.     Item* slotItem = player->getInventoryItem(slot);
  2719.     Item* equipItem = searchForItem(backpack, it.id);
  2720.     if (slotItem && slotItem->getID() == it.id && (!it.stackable || slotItem->getItemCount() == 100 || !equipItem)) {
  2721.         internalMoveItem(slotItem->getParent(), player, CONST_SLOT_WHEREEVER, slotItem, slotItem->getItemCount(), nullptr);
  2722.     } else if (equipItem) {
  2723.         internalMoveItem(equipItem->getParent(), player, slot, equipItem, equipItem->getItemCount(), nullptr);
  2724.     }
  2725. }
  2726.  
  2727. void Game::playerMove(uint32_t playerId, Direction direction)
  2728. {
  2729.     Player* player = getPlayerByID(playerId);
  2730.     if (!player) {
  2731.         return;
  2732.     }
  2733.  
  2734.     player->resetIdleTime();
  2735.     player->setNextWalkActionTask(nullptr);
  2736.  
  2737.     player->startAutoWalk(std::forward_list<Direction> { direction });
  2738. }
  2739.  
  2740. bool Game::playerBroadcastMessage(Player* player, const std::string& text) const
  2741. {
  2742.     if (!player->hasFlag(PlayerFlag_CanBroadcast)) {
  2743.         return false;
  2744.     }
  2745.  
  2746.     SPDLOG_INFO("{} broadcasted: {}", player->getName(), text);
  2747.  
  2748.     for (const auto& it : players) {
  2749.         it.second->sendPrivateMessage(player, TALKTYPE_BROADCAST, text);
  2750.     }
  2751.  
  2752.     return true;
  2753. }
  2754.  
  2755. void Game::playerCreatePrivateChannel(uint32_t playerId)
  2756. {
  2757.     Player* player = getPlayerByID(playerId);
  2758.     if (!player || !player->isPremium()) {
  2759.         return;
  2760.     }
  2761.  
  2762.     ChatChannel* channel = g_chat->createChannel(*player, CHANNEL_PRIVATE);
  2763.     if (!channel || !channel->addUser(*player)) {
  2764.         return;
  2765.     }
  2766.  
  2767.     player->sendCreatePrivateChannel(channel->getId(), channel->getName());
  2768. }
  2769.  
  2770. void Game::playerChannelInvite(uint32_t playerId, const std::string& name)
  2771. {
  2772.     Player* player = getPlayerByID(playerId);
  2773.     if (!player) {
  2774.         return;
  2775.     }
  2776.  
  2777.     PrivateChatChannel* channel = g_chat->getPrivateChannel(*player);
  2778.     if (!channel) {
  2779.         return;
  2780.     }
  2781.  
  2782.     Player* invitePlayer = getPlayerByName(name);
  2783.     if (!invitePlayer) {
  2784.         return;
  2785.     }
  2786.  
  2787.     if (player == invitePlayer) {
  2788.         return;
  2789.     }
  2790.  
  2791.     channel->invitePlayer(*player, *invitePlayer);
  2792. }
  2793.  
  2794. void Game::playerChannelExclude(uint32_t playerId, const std::string& name)
  2795. {
  2796.     Player* player = getPlayerByID(playerId);
  2797.     if (!player) {
  2798.         return;
  2799.     }
  2800.  
  2801.     PrivateChatChannel* channel = g_chat->getPrivateChannel(*player);
  2802.     if (!channel) {
  2803.         return;
  2804.     }
  2805.  
  2806.     Player* excludePlayer = getPlayerByName(name);
  2807.     if (!excludePlayer) {
  2808.         return;
  2809.     }
  2810.  
  2811.     if (player == excludePlayer) {
  2812.         return;
  2813.     }
  2814.  
  2815.     channel->excludePlayer(*player, *excludePlayer);
  2816. }
  2817.  
  2818. void Game::playerRequestChannels(uint32_t playerId)
  2819. {
  2820.     Player* player = getPlayerByID(playerId);
  2821.     if (!player) {
  2822.         return;
  2823.     }
  2824.  
  2825.     player->sendChannelsDialog();
  2826. }
  2827.  
  2828. void Game::playerOpenChannel(uint32_t playerId, uint16_t channelId)
  2829. {
  2830.     Player* player = getPlayerByID(playerId);
  2831.     if (!player) {
  2832.         return;
  2833.     }
  2834.  
  2835.     ChatChannel* channel = g_chat->addUserToChannel(*player, channelId);
  2836.     if (!channel) {
  2837.         return;
  2838.     }
  2839.  
  2840.     const InvitedMap* invitedUsers = channel->getInvitedUsers();
  2841.     const UsersMap* users;
  2842.     if (!channel->isPublicChannel()) {
  2843.         users = &channel->getUsers();
  2844.     } else {
  2845.         users = nullptr;
  2846.     }
  2847.  
  2848.     player->sendChannel(channel->getId(), channel->getName(), users, invitedUsers);
  2849. }
  2850.  
  2851. void Game::playerCloseChannel(uint32_t playerId, uint16_t channelId)
  2852. {
  2853.     Player* player = getPlayerByID(playerId);
  2854.     if (!player) {
  2855.         return;
  2856.     }
  2857.  
  2858.     g_chat->removeUserFromChannel(*player, channelId);
  2859. }
  2860.  
  2861. void Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver)
  2862. {
  2863.     Player* player = getPlayerByID(playerId);
  2864.     if (!player) {
  2865.         return;
  2866.     }
  2867.  
  2868.     if (!IOLoginData::formatPlayerName(receiver)) {
  2869.         player->sendCancelMessage("A player with this name does not exist.");
  2870.         return;
  2871.     }
  2872.  
  2873.     if (player->getName() == receiver) {
  2874.         player->sendCancelMessage("You cannot set up a private message channel with yourself.");
  2875.         return;
  2876.     }
  2877.  
  2878.     player->sendOpenPrivateChannel(receiver);
  2879. }
  2880.  
  2881. void Game::playerCloseNpcChannel(uint32_t playerId)
  2882. {
  2883.     Player* player = getPlayerByID(playerId);
  2884.     if (!player) {
  2885.         return;
  2886.     }
  2887.  
  2888.     SpectatorHashSet spectators;
  2889.     map.getSpectators(spectators, player->getPosition());
  2890.     for (Creature* spectator : spectators) {
  2891.         if (Npc* npc = spectator->getNpc()) {
  2892.             npc->onPlayerCloseChannel(player);
  2893.         }
  2894.     }
  2895. }
  2896.  
  2897. void Game::playerReceivePing(uint32_t playerId)
  2898. {
  2899.     Player* player = getPlayerByID(playerId);
  2900.     if (!player) {
  2901.         return;
  2902.     }
  2903.  
  2904.     player->receivePing();
  2905. }
  2906.  
  2907. void Game::playerReceivePingBack(uint32_t playerId)
  2908. {
  2909.     Player* player = getPlayerByID(playerId);
  2910.     if (!player) {
  2911.         return;
  2912.     }
  2913.  
  2914.     player->sendPingBack();
  2915. }
  2916.  
  2917. void Game::playerAutoWalk(uint32_t playerId, const std::forward_list<Direction>& listDir)
  2918. {
  2919.     Player* player = getPlayerByID(playerId);
  2920.     if (!player) {
  2921.         return;
  2922.     }
  2923.  
  2924.     player->resetIdleTime();
  2925.     player->setNextWalkTask(nullptr);
  2926.     player->startAutoWalk(listDir);
  2927. }
  2928.  
  2929. void Game::playerStopAutoWalk(uint32_t playerId)
  2930. {
  2931.     Player* player = getPlayerByID(playerId);
  2932.     if (!player) {
  2933.         return;
  2934.     }
  2935.  
  2936.     player->stopWalk();
  2937. }
  2938.  
  2939. void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint16_t fromSpriteId,
  2940.                            const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId)
  2941. {
  2942.     Player* player = getPlayerByID(playerId);
  2943.     if (!player) {
  2944.         return;
  2945.     }
  2946.  
  2947.     bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0);
  2948.     if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) {
  2949.         return;
  2950.     }
  2951.  
  2952.     Thing* thing = internalGetThing(player, fromPos, fromStackPos, fromSpriteId, STACKPOS_FIND_THING);
  2953.     if (!thing) {
  2954.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  2955.         return;
  2956.     }
  2957.  
  2958.     Item* item = thing->getItem();
  2959.     if (!item || !item->isUseable() || item->getClientID() != fromSpriteId) {
  2960.         player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
  2961.         return;
  2962.     }
  2963.  
  2964.     Position walkToPos = fromPos;
  2965.     ReturnValue ret = g_actions->canUse(player, fromPos);
  2966.     if (ret == RETURNVALUE_NOERROR) {
  2967.         ret = g_actions->canUse(player, toPos, item);
  2968.         if (ret == RETURNVALUE_TOOFARAWAY) {
  2969.             walkToPos = toPos;
  2970.         }
  2971.     }
  2972.  
  2973.     const ItemType& it = Item::items[item->getID()];
  2974.     if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  2975.         if (player->walkExhausted()) {
  2976.             player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
  2977.             return;
  2978.         }
  2979.     }
  2980.  
  2981.     if (ret != RETURNVALUE_NOERROR) {
  2982.         if (ret == RETURNVALUE_TOOFARAWAY) {
  2983.             Position itemPos = fromPos;
  2984.             uint8_t itemStackPos = fromStackPos;
  2985.  
  2986.             if (fromPos.x != 0xFFFF && toPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) &&
  2987.                     !Position::areInRange<1, 1, 0>(fromPos, toPos)) {
  2988.                 Item* moveItem = nullptr;
  2989.  
  2990.                 ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem);
  2991.                 if (ret != RETURNVALUE_NOERROR) {
  2992.                     player->sendCancelMessage(ret);
  2993.                     return;
  2994.                 }
  2995.  
  2996.                 //changing the position since its now in the inventory of the player
  2997.                 internalGetPosition(moveItem, itemPos, itemStackPos);
  2998.             }
  2999.  
  3000.             std::forward_list<Direction> listDir;
  3001.             if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) {
  3002.                 g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
  3003.  
  3004.                 SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItemEx, this,
  3005.                                       playerId, itemPos, itemStackPos, fromSpriteId, toPos, toStackPos, toSpriteId));
  3006.                 if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3007.                     player->setNextPotionActionTask(task);
  3008.                 } else {
  3009.                     player->setNextWalkActionTask(task);
  3010.                 }
  3011.             } else {
  3012.                 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  3013.             }
  3014.             return;
  3015.         }
  3016.  
  3017.         player->sendCancelMessage(ret);
  3018.         return;
  3019.     }
  3020.  
  3021.     bool canDoAction = player->canDoAction();
  3022.     if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3023.         canDoAction = player->canDoPotionAction();
  3024.     }
  3025.  
  3026.     if (!canDoAction) {
  3027.         uint32_t delay = player->getNextActionTime();
  3028.         if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3029.             delay = player->getNextPotionActionTime();
  3030.         }
  3031.         SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItemEx, this,
  3032.                               playerId, fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId));
  3033.         if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3034.             player->setNextPotionActionTask(task);
  3035.         } else {
  3036.             player->setNextActionTask(task);
  3037.         }
  3038.         return;
  3039.     }
  3040.  
  3041.     player->resetIdleTime();
  3042.     if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3043.         player->setNextPotionActionTask(nullptr);
  3044.     } else {
  3045.         player->setNextActionTask(nullptr);
  3046.     }
  3047.  
  3048.     g_actions->useItemEx(player, fromPos, toPos, toStackPos, item, isHotkey);
  3049. }
  3050.  
  3051. void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos,
  3052.                          uint8_t index, uint16_t spriteId)
  3053. {
  3054.     Player* player = getPlayerByID(playerId);
  3055.     if (!player) {
  3056.         return;
  3057.     }
  3058.  
  3059.     bool isHotkey = (pos.x == 0xFFFF && pos.y == 0 && pos.z == 0);
  3060.     if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) {
  3061.         return;
  3062.     }
  3063.  
  3064.     Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_FIND_THING);
  3065.     if (!thing) {
  3066.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3067.         return;
  3068.     }
  3069.  
  3070.     Item* item = thing->getItem();
  3071.     if (!item || item->isUseable() || item->getClientID() != spriteId) {
  3072.         player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
  3073.         return;
  3074.     }
  3075.  
  3076.     const ItemType& it = Item::items[item->getID()];
  3077.     if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3078.         if (player->walkExhausted()) {
  3079.             player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
  3080.             return;
  3081.         }
  3082.     }
  3083.  
  3084.     ReturnValue ret = g_actions->canUse(player, pos);
  3085.     if (ret != RETURNVALUE_NOERROR) {
  3086.         if (ret == RETURNVALUE_TOOFARAWAY) {
  3087.             std::forward_list<Direction> listDir;
  3088.             if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
  3089.                 g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk,
  3090.                                                 this, player->getID(), listDir)));
  3091.  
  3092.                 SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItem, this,
  3093.                                       playerId, pos, stackPos, index, spriteId));
  3094.                 if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3095.                     player->setNextPotionActionTask(task);
  3096.                 } else {
  3097.                     player->setNextWalkActionTask(task);
  3098.                 }
  3099.                 return;
  3100.             }
  3101.  
  3102.             ret = RETURNVALUE_THEREISNOWAY;
  3103.         }
  3104.  
  3105.         player->sendCancelMessage(ret);
  3106.         return;
  3107.     }
  3108.  
  3109.     bool canDoAction = player->canDoAction();
  3110.     if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3111.         canDoAction = player->canDoPotionAction();
  3112.     }
  3113.  
  3114.     if (!canDoAction) {
  3115.         uint32_t delay = player->getNextActionTime();
  3116.         if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3117.             delay = player->getNextPotionActionTime();
  3118.         }
  3119.         SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItem, this,
  3120.                               playerId, pos, stackPos, index, spriteId));
  3121.         if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3122.             player->setNextPotionActionTask(task);
  3123.         } else {
  3124.             player->setNextActionTask(task);
  3125.         }
  3126.         return;
  3127.     }
  3128.  
  3129.     player->resetIdleTime();
  3130.     player->setNextActionTask(nullptr);
  3131.  
  3132.     g_actions->useItem(player, pos, index, item, isHotkey);
  3133. }
  3134.  
  3135. void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId)
  3136. {
  3137.     Player* player = getPlayerByID(playerId);
  3138.     if (!player) {
  3139.         return;
  3140.     }
  3141.  
  3142.     Creature* creature = getCreatureByID(creatureId);
  3143.     if (!creature) {
  3144.         return;
  3145.     }
  3146.  
  3147.     if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) {
  3148.         return;
  3149.     }
  3150.  
  3151.     bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0);
  3152.     if (!g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) {
  3153.         if (creature->getPlayer() || isHotkey) {
  3154.             player->sendCancelMessage(RETURNVALUE_DIRECTPLAYERSHOOT);
  3155.             return;
  3156.         }
  3157.     }
  3158.  
  3159.     Thing* thing = internalGetThing(player, fromPos, fromStackPos, spriteId, STACKPOS_FIND_THING);
  3160.     if (!thing) {
  3161.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3162.         return;
  3163.     }
  3164.  
  3165.     Item* item = thing->getItem();
  3166.     if (!item || !item->isUseable() || item->getClientID() != spriteId) {
  3167.         player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
  3168.         return;
  3169.     }
  3170.  
  3171.     const ItemType& it = Item::items[item->getID()];
  3172.     if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3173.         if (player->walkExhausted()) {
  3174.             player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
  3175.             return;
  3176.         }
  3177.     }
  3178.     Position toPos = creature->getPosition();
  3179.     Position walkToPos = fromPos;
  3180.     ReturnValue ret = g_actions->canUse(player, fromPos);
  3181.     if (ret == RETURNVALUE_NOERROR) {
  3182.         ret = g_actions->canUse(player, toPos, item);
  3183.         if (ret == RETURNVALUE_TOOFARAWAY) {
  3184.             walkToPos = toPos;
  3185.         }
  3186.     }
  3187.  
  3188.     if (ret != RETURNVALUE_NOERROR) {
  3189.         if (ret == RETURNVALUE_TOOFARAWAY) {
  3190.             Position itemPos = fromPos;
  3191.             uint8_t itemStackPos = fromStackPos;
  3192.  
  3193.             if (fromPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && !Position::areInRange<1, 1, 0>(fromPos, toPos)) {
  3194.                 Item* moveItem = nullptr;
  3195.                 ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem);
  3196.                 if (ret != RETURNVALUE_NOERROR) {
  3197.                     player->sendCancelMessage(ret);
  3198.                     return;
  3199.                 }
  3200.  
  3201.                 //changing the position since its now in the inventory of the player
  3202.                 internalGetPosition(moveItem, itemPos, itemStackPos);
  3203.             }
  3204.  
  3205.             std::forward_list<Direction> listDir;
  3206.             if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) {
  3207.                 g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk,
  3208.                                                 this, player->getID(), listDir)));
  3209.  
  3210.                 SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseWithCreature, this,
  3211.                                       playerId, itemPos, itemStackPos, creatureId, spriteId));
  3212.                 if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3213.                     player->setNextPotionActionTask(task);
  3214.                 } else {
  3215.                     player->setNextWalkActionTask(task);
  3216.                 }
  3217.             } else {
  3218.                 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  3219.             }
  3220.             return;
  3221.         }
  3222.  
  3223.         player->sendCancelMessage(ret);
  3224.         return;
  3225.     }
  3226.  
  3227.     bool canDoAction = player->canDoAction();
  3228.     if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3229.         canDoAction = player->canDoPotionAction();
  3230.     }
  3231.  
  3232.     if (!canDoAction) {
  3233.         uint32_t delay = player->getNextActionTime();
  3234.         if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3235.             delay = player->getNextPotionActionTime();
  3236.         }
  3237.         SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseWithCreature, this,
  3238.                               playerId, fromPos, fromStackPos, creatureId, spriteId));
  3239.  
  3240.         if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3241.             player->setNextPotionActionTask(task);
  3242.         } else {
  3243.             player->setNextActionTask(task);
  3244.         }
  3245.         return;
  3246.     }
  3247.  
  3248.     player->resetIdleTime();
  3249.     if (it.isRune() || it.type == ITEM_TYPE_POTION) {
  3250.         player->setNextPotionActionTask(nullptr);
  3251.     } else {
  3252.         player->setNextActionTask(nullptr);
  3253.     }
  3254.  
  3255.     g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->getThingIndex(creature), item, isHotkey, creature);
  3256. }
  3257.  
  3258. void Game::playerCloseContainer(uint32_t playerId, uint8_t cid)
  3259. {
  3260.     Player* player = getPlayerByID(playerId);
  3261.     if (!player) {
  3262.         return;
  3263.     }
  3264.  
  3265.     player->closeContainer(cid);
  3266.     player->sendCloseContainer(cid);
  3267. }
  3268.  
  3269. void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid)
  3270. {
  3271.     Player* player = getPlayerByID(playerId);
  3272.     if (!player) {
  3273.         return;
  3274.     }
  3275.  
  3276.     Container* container = player->getContainerByID(cid);
  3277.     if (!container) {
  3278.         return;
  3279.     }
  3280.  
  3281.     Container* parentContainer = dynamic_cast<Container*>(container->getRealParent());
  3282.     if (!parentContainer) {
  3283.         Tile* tile = container->getTile();
  3284.         if (!tile) {
  3285.             return;
  3286.         }
  3287.  
  3288.         if (!g_events->eventPlayerOnBrowseField(player, tile->getPosition())) {
  3289.             return;
  3290.         }
  3291.  
  3292.         auto it = browseFields.find(tile);
  3293.         if (it == browseFields.end()) {
  3294.             parentContainer = new Container(tile);
  3295.             parentContainer->incrementReferenceCounter();
  3296.             browseFields[tile] = parentContainer;
  3297.             g_scheduler.addEvent(createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition())));
  3298.         } else {
  3299.             parentContainer = it->second;
  3300.         }
  3301.     }
  3302.  
  3303.     if (parentContainer->hasPagination() && parentContainer->hasParent()) {
  3304.         uint16_t indexContainer = std::floor(parentContainer->getThingIndex(container) / parentContainer->capacity()) * parentContainer->capacity();
  3305.         player->addContainer(cid, parentContainer);
  3306.  
  3307.         player->setContainerIndex(cid, indexContainer);
  3308.         player->sendContainer(cid, parentContainer, parentContainer->hasParent(), indexContainer);
  3309.     } else {
  3310.         player->addContainer(cid, parentContainer);
  3311.         player->sendContainer(cid, parentContainer, parentContainer->hasParent(), player->getContainerIndex(cid));
  3312.     }
  3313. }
  3314.  
  3315. void Game::playerUpdateContainer(uint32_t playerId, uint8_t cid)
  3316. {
  3317.     Player* player = getPlayerByGUID(playerId);
  3318.     if (!player) {
  3319.         return;
  3320.     }
  3321.  
  3322.     Container* container = player->getContainerByID(cid);
  3323.     if (!container) {
  3324.         return;
  3325.     }
  3326.  
  3327.     player->sendContainer(cid, container, container->hasParent(), player->getContainerIndex(cid));
  3328. }
  3329.  
  3330. void Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId)
  3331. {
  3332.     Player* player = getPlayerByID(playerId);
  3333.     if (!player) {
  3334.         return;
  3335.     }
  3336.  
  3337.     Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM);
  3338.     if (!thing) {
  3339.         return;
  3340.     }
  3341.  
  3342.     Item* item = thing->getItem();
  3343.     if (!item || item->getClientID() != spriteId || !item->isRotatable() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
  3344.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3345.         return;
  3346.     }
  3347.  
  3348.     if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
  3349.         std::forward_list<Direction> listDir;
  3350.         if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
  3351.             g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk,
  3352.                                             this, player->getID(), listDir)));
  3353.  
  3354.             SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRotateItem, this,
  3355.                                   playerId, pos, stackPos, spriteId));
  3356.             player->setNextWalkActionTask(task);
  3357.         } else {
  3358.             player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  3359.         }
  3360.         return;
  3361.     }
  3362.  
  3363.     uint16_t newId = Item::items[item->getID()].rotateTo;
  3364.     if (newId != 0) {
  3365.         transformItem(item, newId);
  3366.     }
  3367. }
  3368.  
  3369. void Game::playerConfigureShowOffSocket(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId)
  3370. {
  3371.     Player* player = getPlayerByID(playerId);
  3372.     if (!player || pos.x == 0xFFFF) {
  3373.         return;
  3374.     }
  3375.  
  3376.     Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM);
  3377.     if (!thing) {
  3378.         return;
  3379.     }
  3380.  
  3381.     Item* item = thing->getItem();
  3382.     if (!item || item->getClientID() != spriteId || !item->isPodium() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
  3383.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3384.         return;
  3385.     }
  3386.  
  3387.     if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
  3388.         std::forward_list<Direction> listDir;
  3389.         if (player->getPathTo(pos, listDir, 0, 1, true, false)) {
  3390.             g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
  3391.             SchedulerTask* task = createSchedulerTask(400, std::bind(
  3392.                                       &Game::playerBrowseField, this, playerId, pos
  3393.                                   ));
  3394.             player->setNextWalkActionTask(task);
  3395.  
  3396.         } else {
  3397.             player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  3398.         }
  3399.         return;
  3400.     }
  3401.  
  3402.     player->sendPodiumWindow(item, pos, spriteId, stackPos);
  3403. }
  3404.  
  3405. void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t& outfit, const Position& pos, uint8_t stackPos, const uint16_t spriteId, uint8_t podiumVisible, uint8_t direction)
  3406. {
  3407.     Player* player = getPlayerByID(playerId);
  3408.     if (!player || pos.x == 0xFFFF) {
  3409.         return;
  3410.     }
  3411.  
  3412.     Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM);
  3413.     if (!thing) {
  3414.         return;
  3415.     }
  3416.  
  3417.     Item* item = thing->getItem();
  3418.     if (!item || item->getClientID() != spriteId || !item->isPodium() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
  3419.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3420.         return;
  3421.     }
  3422.  
  3423.     Tile* tile = dynamic_cast<Tile*>(item->getParent());
  3424.     if (!tile) {
  3425.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3426.         return;
  3427.     }
  3428.  
  3429.     if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
  3430.         std::forward_list<Direction> listDir;
  3431.         if (player->getPathTo(pos, listDir, 0, 1, true, false)) {
  3432.             g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
  3433.             SchedulerTask* task = createSchedulerTask(400, std::bind(
  3434.                                       &Game::playerBrowseField, this, playerId, pos
  3435.                                   ));
  3436.             player->setNextWalkActionTask(task);
  3437.         } else {
  3438.             player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  3439.         }
  3440.         return;
  3441.     }
  3442.  
  3443.     if (!player->canWear(outfit.lookType, outfit.lookAddons)) {
  3444.         outfit.lookType = 0;
  3445.         outfit.lookAddons = 0;
  3446.     }
  3447.  
  3448.     Mount* mount = mounts.getMountByClientID(outfit.lookMount);
  3449.     if (!mount || !player->hasMount(mount)) {
  3450.         outfit.lookMount = 0;
  3451.     }
  3452.  
  3453.     std::string key; // Too lazy to fix it :) let's just use temporary key variable
  3454.     if (outfit.lookType != 0) {
  3455.         key = "LookType"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookType));
  3456.         key = "LookHead"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookHead));
  3457.         key = "LookBody"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookBody));
  3458.         key = "LookLegs"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookLegs));
  3459.         key = "LookFeet"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookFeet));
  3460.         key = "LookAddons"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookAddons));
  3461.     } else {
  3462.         item->removeCustomAttribute("LookType");
  3463.     }
  3464.  
  3465.     if (outfit.lookMount != 0) {
  3466.         key = "LookMount"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookMount));
  3467.         key = "LookMountHead"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookMountHead));
  3468.         key = "LookMountBody"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookMountBody));
  3469.         key = "LookMountLegs"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookMountLegs));
  3470.         key = "LookMountFeet"; item->setCustomAttribute(key, static_cast<int64_t>(outfit.lookMountFeet));
  3471.     } else {
  3472.         item->removeCustomAttribute("LookMount");
  3473.     }
  3474.  
  3475.     key = "PodiumVisible"; item->setCustomAttribute(key, static_cast<int64_t>(podiumVisible));
  3476.     key = "LookDirection"; item->setCustomAttribute(key, static_cast<int64_t>(direction));
  3477.  
  3478.     SpectatorHashSet spectators;
  3479.     g_game.map.getSpectators(spectators, pos, true);
  3480.  
  3481.     // send to client
  3482.     for (Creature* spectator : spectators) {
  3483.         if (Player* tmpPlayer = spectator->getPlayer()) {
  3484.             tmpPlayer->sendUpdateTileItem(tile, pos, item);
  3485.         }
  3486.     }
  3487. }
  3488.  
  3489. void Game::playerWrapableItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId)
  3490. {
  3491.     Player* player = getPlayerByID(playerId);
  3492.     if (!player) {
  3493.         return;
  3494.     }
  3495.  
  3496.     House* house = map.houses.getHouseByPlayerId(player->getGUID());
  3497.     if (!house) {
  3498.         player->sendCancelMessage("You don't own a house, you need own a house to use this.");
  3499.         return;
  3500.     }
  3501.  
  3502.     Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM);
  3503.     if (!thing) {
  3504.         return;
  3505.     }
  3506.  
  3507.     Item* item = thing->getItem();
  3508.     Tile* tile = map.getTile(item->getPosition());
  3509.     HouseTile* houseTile = dynamic_cast<HouseTile*>(tile);
  3510.     if (!tile->hasFlag(TILESTATE_PROTECTIONZONE) || !houseTile) {
  3511.         player->sendCancelMessage("You may construct this only inside a house.");
  3512.         return;
  3513.     }
  3514.     if (houseTile->getHouse() != house) {
  3515.         player->sendCancelMessage("Only owners can wrap/unwrap inside a house.");
  3516.             return;
  3517.     }
  3518.  
  3519.     if (!item || item->getClientID() != spriteId || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) || (!item->isWrapable() && item->getID() != 26054)) {
  3520.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3521.         return;
  3522.     }
  3523.  
  3524.     if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
  3525.         std::forward_list<Direction> listDir;
  3526.         if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
  3527.             g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk,
  3528.                 this, player->getID(), listDir)));
  3529.  
  3530.             SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerWrapableItem, this,
  3531.                 playerId, pos, stackPos, spriteId));
  3532.             player->setNextWalkActionTask(task);
  3533.         } else {
  3534.             player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  3535.         }
  3536.         return;
  3537.     }
  3538.  
  3539.     const Container* container = item->getContainer();
  3540.     if (container && container->getItemHoldingCount() > 0) {
  3541.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3542.         return;
  3543.     }
  3544.  
  3545.     if ((item->getHoldingPlayer() && item->getID() == 26054) || (tile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && !item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID))) {
  3546.         player->sendCancelMessage("You can only wrap/unwrap in the floor.");
  3547.         return;
  3548.     }
  3549.  
  3550.     std::string itemName = item->getName();
  3551.     const ItemAttributes::CustomAttribute* attr = item->getCustomAttribute("unWrapId");
  3552.     uint16_t unWrapId = 0;
  3553.     if (attr != nullptr) {
  3554.         uint32_t tmp = static_cast<uint32_t>(boost::get<int64_t>(attr->value));
  3555.         unWrapId = (uint16_t)tmp;
  3556.     }
  3557.  
  3558.   // prevent to wrap a filled bath tube
  3559.   if (item->getID() == 29313) {
  3560.     player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3561.     return;
  3562.   }
  3563.  
  3564.     if (item->isWrapable() && item->getID() != TRANSFORM_BOX_ID) {
  3565.         uint16_t hiddenCharges = 0;
  3566.         if (isCaskItem(item->getID())) {
  3567.             hiddenCharges = item->getSubType();
  3568.         }
  3569.         uint16_t oldItemID = item->getID();
  3570.         addMagicEffect(item->getPosition(), CONST_ME_POFF);
  3571.         Item* newItem = transformItem(item, 26054);
  3572.         ItemAttributes::CustomAttribute val;
  3573.         val.set<int64_t>(oldItemID);
  3574.         std::string key = "unWrapId";
  3575.         newItem->setCustomAttribute(key, val);
  3576.         item->setSpecialDescription("Unwrap it in your own house to create a <" + itemName + ">.");
  3577.         if (hiddenCharges > 0) {
  3578.             item->setDate(hiddenCharges);
  3579.         }
  3580.         startDecay(item);
  3581.     }
  3582.     else if (item->getID() == TRANSFORM_BOX_ID && unWrapId != 0) {
  3583.         uint16_t hiddenCharges = item->getDate();
  3584.         item->removeAttribute(ITEM_ATTRIBUTE_DESCRIPTION);
  3585.         addMagicEffect(item->getPosition(), CONST_ME_POFF);
  3586.         transformItem(item, unWrapId);
  3587.         if (hiddenCharges > 0 && isCaskItem(unWrapId)) {
  3588.             item->setSubType(hiddenCharges);
  3589.         }
  3590.         item->removeCustomAttribute("unWrapId");
  3591.         startDecay(item);
  3592.     }
  3593. }
  3594.  
  3595. void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text)
  3596. {
  3597.     Player* player = getPlayerByID(playerId);
  3598.     if (!player) {
  3599.         return;
  3600.     }
  3601.  
  3602.     uint16_t maxTextLength = 0;
  3603.     uint32_t internalWindowTextId = 0;
  3604.  
  3605.     Item* writeItem = player->getWriteItem(internalWindowTextId, maxTextLength);
  3606.     if (text.length() > maxTextLength || windowTextId != internalWindowTextId) {
  3607.         return;
  3608.     }
  3609.  
  3610.     if (!writeItem || writeItem->isRemoved()) {
  3611.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3612.         return;
  3613.     }
  3614.  
  3615.     Cylinder* topParent = writeItem->getTopParent();
  3616.  
  3617.     Player* owner = dynamic_cast<Player*>(topParent);
  3618.     if (owner && owner != player) {
  3619.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3620.         return;
  3621.     }
  3622.  
  3623.     if (!Position::areInRange<1, 1, 0>(writeItem->getPosition(), player->getPosition())) {
  3624.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3625.         return;
  3626.     }
  3627.  
  3628.     for (auto creatureEvent : player->getCreatureEvents(CREATURE_EVENT_TEXTEDIT)) {
  3629.         if (!creatureEvent->executeTextEdit(player, writeItem, text)) {
  3630.             player->setWriteItem(nullptr);
  3631.             return;
  3632.         }
  3633.     }
  3634.  
  3635.     if (!text.empty()) {
  3636.         if (writeItem->getText() != text) {
  3637.             writeItem->setText(text);
  3638.             writeItem->setWriter(player->getName());
  3639.             writeItem->setDate(time(nullptr));
  3640.         }
  3641.     } else {
  3642.         writeItem->resetText();
  3643.         writeItem->resetWriter();
  3644.         writeItem->resetDate();
  3645.     }
  3646.  
  3647.     uint16_t newId = Item::items[writeItem->getID()].writeOnceItemId;
  3648.     if (newId != 0) {
  3649.         transformItem(writeItem, newId);
  3650.     }
  3651.  
  3652.     player->setWriteItem(nullptr);
  3653. }
  3654.  
  3655. void Game::playerBrowseField(uint32_t playerId, const Position& pos)
  3656. {
  3657.     Player* player = getPlayerByID(playerId);
  3658.     if (!player) {
  3659.         return;
  3660.     }
  3661.  
  3662.     const Position& playerPos = player->getPosition();
  3663.     if (playerPos.z != pos.z) {
  3664.         player->sendCancelMessage(playerPos.z > pos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS);
  3665.         return;
  3666.     }
  3667.  
  3668.     if (!Position::areInRange<1, 1>(playerPos, pos)) {
  3669.         std::forward_list<Direction> listDir;
  3670.         if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
  3671.             g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk,
  3672.                                             this, player->getID(), listDir)));
  3673.             SchedulerTask* task = createSchedulerTask(400, std::bind(
  3674.                                       &Game::playerBrowseField, this, playerId, pos
  3675.                                   ));
  3676.             player->setNextWalkActionTask(task);
  3677.         } else {
  3678.             player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  3679.         }
  3680.         return;
  3681.     }
  3682.  
  3683.     Tile* tile = map.getTile(pos);
  3684.     if (!tile) {
  3685.         return;
  3686.     }
  3687.  
  3688.     if (!g_events->eventPlayerOnBrowseField(player, pos)) {
  3689.         return;
  3690.     }
  3691.  
  3692.     Container* container;
  3693.  
  3694.     auto it = browseFields.find(tile);
  3695.     if (it == browseFields.end()) {
  3696.         container = new Container(tile);
  3697.         container->incrementReferenceCounter();
  3698.         browseFields[tile] = container;
  3699.         g_scheduler.addEvent(createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition())));
  3700.     } else {
  3701.         container = it->second;
  3702.     }
  3703.  
  3704.     uint8_t dummyContainerId = 0xF - ((pos.x % 3) * 3 + (pos.y % 3));
  3705.     Container* openContainer = player->getContainerByID(dummyContainerId);
  3706.     if (openContainer) {
  3707.         player->onCloseContainer(openContainer);
  3708.         player->closeContainer(dummyContainerId);
  3709.     } else {
  3710.         player->addContainer(dummyContainerId, container);
  3711.         player->sendContainer(dummyContainerId, container, false, 0);
  3712.     }
  3713. }
  3714.  
  3715. void Game::playerStowItem(Player* player, const Position& pos, uint16_t spriteId, uint8_t stackpos, uint8_t count, bool allItems)
  3716. {
  3717.     if (!player->isPremium()) {
  3718.         player->sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
  3719.         return;
  3720.     }
  3721.  
  3722.     Thing* thing = internalGetThing(player, pos, stackpos, 0, STACKPOS_TOPDOWN_ITEM);
  3723.     if (!thing)
  3724.         return;
  3725.  
  3726.     Item* item = thing->getItem();
  3727.     if (!item || item->getClientID() != spriteId || item->getItemCount() < count) {
  3728.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3729.         return;
  3730.     }
  3731.  
  3732.     if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
  3733.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3734.         return;
  3735.     }
  3736.     player->stowItem(item, count, allItems);
  3737. }
  3738.  
  3739. void Game::playerStashWithdraw(Player* player, uint16_t spriteId, uint32_t count, uint8_t)
  3740. {
  3741.     if (player->hasFlag(PlayerFlag_CannotPickupItem)) {
  3742.         return;
  3743.     }
  3744.  
  3745.     const ItemType& it = Item::items.getItemIdByClientId(spriteId);
  3746.     if (it.id == 0 || count == 0) {
  3747.         return;
  3748.     }
  3749.  
  3750.     uint16_t freeSlots = player->getFreeBackpackSlots();
  3751.     Container* stashContainer = player->getLootContainer(OBJECTCATEGORY_STASHRETRIEVE);
  3752.     if (stashContainer && !(player->quickLootFallbackToMainContainer)) {
  3753.         freeSlots = stashContainer->getFreeSlots();
  3754.     }
  3755.  
  3756.     if (freeSlots == 0) {
  3757.         player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
  3758.         return;
  3759.     }
  3760.  
  3761.     if (player->getFreeCapacity() < 100) {
  3762.         player->sendCancelMessage(RETURNVALUE_NOTENOUGHCAPACITY);
  3763.         return;
  3764.     }
  3765.  
  3766.     int32_t NDSlots = ((freeSlots) - (count < 100 ? 1 : (count / 100)));
  3767.     uint32_t SlotsWith = count;
  3768.     uint32_t noSlotsWith = 0;
  3769.  
  3770.     if (NDSlots <= 0) {
  3771.         SlotsWith = (freeSlots * 100);
  3772.         noSlotsWith = (count - SlotsWith);
  3773.     }
  3774.  
  3775.     uint32_t capWith = count;
  3776.     uint32_t noCapWith = 0;
  3777.     if (player->getFreeCapacity() < (count * it.weight)) {
  3778.         capWith = (player->getFreeCapacity() / it.weight);
  3779.         noCapWith = (count - capWith);
  3780.     }
  3781.  
  3782.     std::stringstream ss;
  3783.     uint32_t WithdrawCount = (SlotsWith > capWith ? capWith : SlotsWith);
  3784.     uint32_t NoWithdrawCount = (noSlotsWith < noCapWith ? noCapWith : noSlotsWith);
  3785.     const char * NoWithdrawMsg = (noSlotsWith < noCapWith ? "capacity" : "slots");
  3786.  
  3787.     if (WithdrawCount != count) {
  3788.         ss << "Retrieved " << WithdrawCount << "x " << it.name << ".\n";
  3789.         ss << NoWithdrawCount << "x are impossible to retrieve due to insufficient inventory " << NoWithdrawMsg << ".";
  3790.     } else {
  3791.         ss << "Retrieved " << WithdrawCount << "x " << it.name << '.';
  3792.     }
  3793.  
  3794.     player->sendTextMessage(MESSAGE_STATUS, ss.str());
  3795.  
  3796.     if (player->withdrawItem(spriteId, WithdrawCount)) {
  3797.         player->addItemFromStash(it.id, WithdrawCount);
  3798.     } else {
  3799.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3800.     }
  3801. }
  3802.  
  3803. void Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index)
  3804. {
  3805.     Player* player = getPlayerByID(playerId);
  3806.     if (!player) {
  3807.         return;
  3808.     }
  3809.  
  3810.     Container* container = player->getContainerByID(containerId);
  3811.     if (!container || !container->hasPagination()) {
  3812.         return;
  3813.     }
  3814.  
  3815.     if ((index % container->capacity()) != 0 || index >= container->size()) {
  3816.         return;
  3817.     }
  3818.  
  3819.     player->setContainerIndex(containerId, index);
  3820.     player->sendContainer(containerId, container, container->hasParent(), index);
  3821. }
  3822.  
  3823. void Game::playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text)
  3824. {
  3825.     Player* player = getPlayerByID(playerId);
  3826.     if (!player) {
  3827.         return;
  3828.     }
  3829.  
  3830.     uint32_t internalWindowTextId;
  3831.     uint32_t internalListId;
  3832.  
  3833.     House* house = player->getEditHouse(internalWindowTextId, internalListId);
  3834.     if (house && house->canEditAccessList(internalListId, player) && internalWindowTextId == windowTextId && listId == 0) {
  3835.         house->setAccessList(internalListId, text);
  3836.     }
  3837.  
  3838.     player->setEditHouse(nullptr);
  3839. }
  3840.  
  3841. void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos,
  3842.                               uint32_t tradePlayerId, uint16_t spriteId)
  3843. {
  3844.     Player* player = getPlayerByID(playerId);
  3845.     if (!player) {
  3846.         return;
  3847.     }
  3848.  
  3849.     Player* tradePartner = getPlayerByID(tradePlayerId);
  3850.     if (!tradePartner || tradePartner == player) {
  3851.         player->sendTextMessage(MESSAGE_FAILURE, "Sorry, not possible.");
  3852.         return;
  3853.     }
  3854.  
  3855.     if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player->getPosition())) {
  3856.         std::ostringstream ss;
  3857.         ss << tradePartner->getName() << " tells you to move closer.";
  3858.         player->sendTextMessage(MESSAGE_TRADE, ss.str());
  3859.         return;
  3860.     }
  3861.  
  3862.     if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) {
  3863.         player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE);
  3864.         return;
  3865.     }
  3866.  
  3867.     Thing* tradeThing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM);
  3868.     if (!tradeThing) {
  3869.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3870.         return;
  3871.     }
  3872.  
  3873.     Item* tradeItem = tradeThing->getItem();
  3874.     if (tradeItem->getClientID() != spriteId || !tradeItem->isPickupable() || tradeItem->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
  3875.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3876.         return;
  3877.     }
  3878.  
  3879.   if (g_config.getBoolean(ConfigManager::ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS)) {
  3880.         if (HouseTile* houseTile = dynamic_cast<HouseTile*>(tradeItem->getTile())) {
  3881.             House* house = houseTile->getHouse();
  3882.             if (house && !house->isInvited(player)) {
  3883.                 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  3884.                 return;
  3885.             }
  3886.         }
  3887.     }
  3888.  
  3889.     const Position& playerPosition = player->getPosition();
  3890.     const Position& tradeItemPosition = tradeItem->getPosition();
  3891.     if (playerPosition.z != tradeItemPosition.z) {
  3892.         player->sendCancelMessage(playerPosition.z > tradeItemPosition.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS);
  3893.         return;
  3894.     }
  3895.  
  3896.     if (!Position::areInRange<1, 1>(tradeItemPosition, playerPosition)) {
  3897.         std::forward_list<Direction> listDir;
  3898.         if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
  3899.             g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk,
  3900.                                             this, player->getID(), listDir)));
  3901.  
  3902.             SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRequestTrade, this,
  3903.                                   playerId, pos, stackPos, tradePlayerId, spriteId));
  3904.             player->setNextWalkActionTask(task);
  3905.         } else {
  3906.             player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  3907.         }
  3908.         return;
  3909.     }
  3910.  
  3911.     Container* tradeItemContainer = tradeItem->getContainer();
  3912.     if (tradeItemContainer) {
  3913.         for (const auto& it : tradeItems) {
  3914.             Item* item = it.first;
  3915.             if (tradeItem == item) {
  3916.                 player->sendTextMessage(MESSAGE_TRADE, "This item is already being traded.");
  3917.                 return;
  3918.             }
  3919.  
  3920.             if (tradeItemContainer->isHoldingItem(item)) {
  3921.                 player->sendTextMessage(MESSAGE_TRADE, "This item is already being traded.");
  3922.                 return;
  3923.             }
  3924.  
  3925.             Container* container = item->getContainer();
  3926.             if (container && container->isHoldingItem(tradeItem)) {
  3927.                 player->sendTextMessage(MESSAGE_TRADE, "This item is already being traded.");
  3928.                 return;
  3929.             }
  3930.         }
  3931.     } else {
  3932.         for (const auto& it : tradeItems) {
  3933.             Item* item = it.first;
  3934.             if (tradeItem == item) {
  3935.                 player->sendTextMessage(MESSAGE_TRADE, "This item is already being traded.");
  3936.                 return;
  3937.             }
  3938.  
  3939.             Container* container = item->getContainer();
  3940.             if (container && container->isHoldingItem(tradeItem)) {
  3941.                 player->sendTextMessage(MESSAGE_TRADE, "This item is already being traded.");
  3942.                 return;
  3943.             }
  3944.         }
  3945.     }
  3946.  
  3947.     Container* tradeContainer = tradeItem->getContainer();
  3948.     if (tradeContainer && tradeContainer->getItemHoldingCount() + 1 > 100) {
  3949.         player->sendTextMessage(MESSAGE_TRADE, "You can not trade more than 100 items.");
  3950.         return;
  3951.     }
  3952.  
  3953.     if (!g_events->eventPlayerOnTradeRequest(player, tradePartner, tradeItem)) {
  3954.         return;
  3955.     }
  3956.  
  3957.     internalStartTrade(player, tradePartner, tradeItem);
  3958. }
  3959.  
  3960. bool Game::internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem)
  3961. {
  3962.     if (player->tradeState != TRADE_NONE && !(player->tradeState == TRADE_ACKNOWLEDGE && player->tradePartner == tradePartner)) {
  3963.         player->sendCancelMessage(RETURNVALUE_YOUAREALREADYTRADING);
  3964.         return false;
  3965.     } else if (tradePartner->tradeState != TRADE_NONE && tradePartner->tradePartner != player) {
  3966.         player->sendCancelMessage(RETURNVALUE_THISPLAYERISALREADYTRADING);
  3967.         return false;
  3968.     }
  3969.  
  3970.     player->tradePartner = tradePartner;
  3971.     player->tradeItem = tradeItem;
  3972.     player->tradeState = TRADE_INITIATED;
  3973.     tradeItem->incrementReferenceCounter();
  3974.     tradeItems[tradeItem] = player->getID();
  3975.  
  3976.     player->sendTradeItemRequest(player->getName(), tradeItem, true);
  3977.  
  3978.     if (tradePartner->tradeState == TRADE_NONE) {
  3979.         std::ostringstream ss;
  3980.         ss << player->getName() << " wants to trade with you.";
  3981.         tradePartner->sendTextMessage(MESSAGE_TRANSACTION, ss.str());
  3982.         tradePartner->tradeState = TRADE_ACKNOWLEDGE;
  3983.         tradePartner->tradePartner = player;
  3984.     } else {
  3985.         Item* counterOfferItem = tradePartner->tradeItem;
  3986.         player->sendTradeItemRequest(tradePartner->getName(), counterOfferItem, false);
  3987.         tradePartner->sendTradeItemRequest(player->getName(), tradeItem, false);
  3988.     }
  3989.  
  3990.     return true;
  3991. }
  3992.  
  3993. void Game::playerAcceptTrade(uint32_t playerId)
  3994. {
  3995.     Player* player = getPlayerByID(playerId);
  3996.     if (!player) {
  3997.         return;
  3998.     }
  3999.  
  4000.     if (!(player->getTradeState() == TRADE_ACKNOWLEDGE || player->getTradeState() == TRADE_INITIATED)) {
  4001.         return;
  4002.     }
  4003.  
  4004.     Player* tradePartner = player->tradePartner;
  4005.     if (!tradePartner) {
  4006.         return;
  4007.     }
  4008.  
  4009.     if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) {
  4010.         player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE);
  4011.         return;
  4012.     }
  4013.  
  4014.     player->setTradeState(TRADE_ACCEPT);
  4015.  
  4016.     if (tradePartner->getTradeState() == TRADE_ACCEPT) {
  4017.         Item* tradeItem1 = player->tradeItem;
  4018.         Item* tradeItem2 = tradePartner->tradeItem;
  4019.  
  4020.         if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, tradeItem1, tradeItem2)) {
  4021.             internalCloseTrade(player);
  4022.             return;
  4023.         }
  4024.  
  4025.         player->setTradeState(TRADE_TRANSFER);
  4026.         tradePartner->setTradeState(TRADE_TRANSFER);
  4027.  
  4028.         auto it = tradeItems.find(tradeItem1);
  4029.         if (it != tradeItems.end()) {
  4030.             ReleaseItem(it->first);
  4031.             tradeItems.erase(it);
  4032.         }
  4033.  
  4034.         it = tradeItems.find(tradeItem2);
  4035.         if (it != tradeItems.end()) {
  4036.             ReleaseItem(it->first);
  4037.             tradeItems.erase(it);
  4038.         }
  4039.  
  4040.         bool isSuccess = false;
  4041.  
  4042.         ReturnValue ret1 = internalAddItem(tradePartner, tradeItem1, INDEX_WHEREEVER, 0, true);
  4043.         ReturnValue ret2 = internalAddItem(player, tradeItem2, INDEX_WHEREEVER, 0, true);
  4044.         if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) {
  4045.             ret1 = internalRemoveItem(tradeItem1, tradeItem1->getItemCount(), true);
  4046.             ret2 = internalRemoveItem(tradeItem2, tradeItem2->getItemCount(), true);
  4047.             if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) {
  4048.                 Cylinder* cylinder1 = tradeItem1->getParent();
  4049.                 Cylinder* cylinder2 = tradeItem2->getParent();
  4050.  
  4051.                 uint32_t count1 = tradeItem1->getItemCount();
  4052.                 uint32_t count2 = tradeItem2->getItemCount();
  4053.  
  4054.                 ret1 = internalMoveItem(cylinder1, tradePartner, INDEX_WHEREEVER, tradeItem1, count1, nullptr, FLAG_IGNOREAUTOSTACK, nullptr, tradeItem2);
  4055.                 if (ret1 == RETURNVALUE_NOERROR) {
  4056.                     internalMoveItem(cylinder2, player, INDEX_WHEREEVER, tradeItem2, count2, nullptr, FLAG_IGNOREAUTOSTACK);
  4057.  
  4058.                     tradeItem1->onTradeEvent(ON_TRADE_TRANSFER, tradePartner);
  4059.                     tradeItem2->onTradeEvent(ON_TRADE_TRANSFER, player);
  4060.  
  4061.                     isSuccess = true;
  4062.                 }
  4063.             }
  4064.         }
  4065.  
  4066.         if (!isSuccess) {
  4067.             std::string errorDescription;
  4068.  
  4069.             if (tradePartner->tradeItem) {
  4070.                 errorDescription = getTradeErrorDescription(ret1, tradeItem1);
  4071.                 tradePartner->sendTextMessage(MESSAGE_TRANSACTION, errorDescription);
  4072.                 tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner);
  4073.             }
  4074.  
  4075.             if (player->tradeItem) {
  4076.                 errorDescription = getTradeErrorDescription(ret2, tradeItem2);
  4077.                 player->sendTextMessage(MESSAGE_TRANSACTION, errorDescription);
  4078.                 player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player);
  4079.             }
  4080.         }
  4081.  
  4082.         player->setTradeState(TRADE_NONE);
  4083.         player->tradeItem = nullptr;
  4084.         player->tradePartner = nullptr;
  4085.         player->sendTradeClose();
  4086.  
  4087.         tradePartner->setTradeState(TRADE_NONE);
  4088.         tradePartner->tradeItem = nullptr;
  4089.         tradePartner->tradePartner = nullptr;
  4090.         tradePartner->sendTradeClose();
  4091.     }
  4092. }
  4093.  
  4094. std::string Game::getTradeErrorDescription(ReturnValue ret, Item* item)
  4095. {
  4096.     if (item) {
  4097.         if (ret == RETURNVALUE_NOTENOUGHCAPACITY) {
  4098.             std::ostringstream ss;
  4099.             ss << "You do not have enough capacity to carry";
  4100.  
  4101.             if (item->isStackable() && item->getItemCount() > 1) {
  4102.                 ss << " these objects.";
  4103.             } else {
  4104.                 ss << " this object.";
  4105.             }
  4106.  
  4107.             ss << std::endl << ' ' << item->getWeightDescription();
  4108.             return ss.str();
  4109.         } else if (ret == RETURNVALUE_NOTENOUGHROOM || ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) {
  4110.             std::ostringstream ss;
  4111.             ss << "You do not have enough room to carry";
  4112.  
  4113.             if (item->isStackable() && item->getItemCount() > 1) {
  4114.                 ss << " these objects.";
  4115.             } else {
  4116.                 ss << " this object.";
  4117.             }
  4118.  
  4119.             return ss.str();
  4120.         }
  4121.     }
  4122.     return "Trade could not be completed.";
  4123. }
  4124.  
  4125. void Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index)
  4126. {
  4127.     Player* player = getPlayerByID(playerId);
  4128.     if (!player) {
  4129.         return;
  4130.     }
  4131.  
  4132.     Player* tradePartner = player->tradePartner;
  4133.     if (!tradePartner) {
  4134.         return;
  4135.     }
  4136.  
  4137.     Item* tradeItem;
  4138.     if (lookAtCounterOffer) {
  4139.         tradeItem = tradePartner->getTradeItem();
  4140.     } else {
  4141.         tradeItem = player->getTradeItem();
  4142.     }
  4143.  
  4144.     if (!tradeItem) {
  4145.         return;
  4146.     }
  4147.  
  4148.     const Position& playerPosition = player->getPosition();
  4149.     const Position& tradeItemPosition = tradeItem->getPosition();
  4150.  
  4151.     int32_t lookDistance = std::max<int32_t>(Position::getDistanceX(playerPosition, tradeItemPosition),
  4152.                                              Position::getDistanceY(playerPosition, tradeItemPosition));
  4153.     if (index == 0) {
  4154.         g_events->eventPlayerOnLookInTrade(player, tradePartner, tradeItem, lookDistance);
  4155.         return;
  4156.     }
  4157.  
  4158.     Container* tradeContainer = tradeItem->getContainer();
  4159.     if (!tradeContainer) {
  4160.         return;
  4161.     }
  4162.  
  4163.     std::vector<const Container*> containers {tradeContainer};
  4164.     size_t i = 0;
  4165.     while (i < containers.size()) {
  4166.         const Container* container = containers[i++];
  4167.         for (Item* item : container->getItemList()) {
  4168.             Container* tmpContainer = item->getContainer();
  4169.             if (tmpContainer) {
  4170.                 containers.push_back(tmpContainer);
  4171.             }
  4172.  
  4173.             if (--index == 0) {
  4174.                 g_events->eventPlayerOnLookInTrade(player, tradePartner, item, lookDistance);
  4175.                 return;
  4176.             }
  4177.         }
  4178.     }
  4179. }
  4180.  
  4181. void Game::playerCloseTrade(uint32_t playerId)
  4182. {
  4183.     Player* player = getPlayerByID(playerId);
  4184.     if (!player) {
  4185.         return;
  4186.     }
  4187.  
  4188.     internalCloseTrade(player);
  4189. }
  4190.  
  4191. void Game::internalCloseTrade(Player* player)
  4192. {
  4193.     Player* tradePartner = player->tradePartner;
  4194.     if ((tradePartner && tradePartner->getTradeState() == TRADE_TRANSFER) || player->getTradeState() == TRADE_TRANSFER) {
  4195.         return;
  4196.     }
  4197.  
  4198.     if (player->getTradeItem()) {
  4199.         auto it = tradeItems.find(player->getTradeItem());
  4200.         if (it != tradeItems.end()) {
  4201.             ReleaseItem(it->first);
  4202.             tradeItems.erase(it);
  4203.         }
  4204.  
  4205.         player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player);
  4206.         player->tradeItem = nullptr;
  4207.     }
  4208.  
  4209.     player->setTradeState(TRADE_NONE);
  4210.     player->tradePartner = nullptr;
  4211.  
  4212.     player->sendTextMessage(MESSAGE_FAILURE, "Trade cancelled.");
  4213.     player->sendTradeClose();
  4214.  
  4215.     if (tradePartner) {
  4216.         if (tradePartner->getTradeItem()) {
  4217.             auto it = tradeItems.find(tradePartner->getTradeItem());
  4218.             if (it != tradeItems.end()) {
  4219.                 ReleaseItem(it->first);
  4220.                 tradeItems.erase(it);
  4221.             }
  4222.  
  4223.             tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner);
  4224.             tradePartner->tradeItem = nullptr;
  4225.         }
  4226.  
  4227.         tradePartner->setTradeState(TRADE_NONE);
  4228.         tradePartner->tradePartner = nullptr;
  4229.  
  4230.         tradePartner->sendTextMessage(MESSAGE_FAILURE, "Trade cancelled.");
  4231.         tradePartner->sendTradeClose();
  4232.     }
  4233. }
  4234.  
  4235. void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount,
  4236.                               bool ignoreCap/* = false*/, bool inBackpacks/* = false*/)
  4237. {
  4238.     if (amount == 0 || amount > 100) {
  4239.         return;
  4240.     }
  4241.  
  4242.     Player* player = getPlayerByID(playerId);
  4243.     if (!player) {
  4244.         return;
  4245.     }
  4246.  
  4247.     int32_t onBuy, onSell;
  4248.  
  4249.     Npc* merchant = player->getShopOwner(onBuy, onSell);
  4250.     if (!merchant) {
  4251.         return;
  4252.     }
  4253.  
  4254.     const ItemType& it = Item::items.getItemIdByClientId(spriteId);
  4255.     if (it.id == 0) {
  4256.         return;
  4257.     }
  4258.  
  4259.     uint8_t subType;
  4260.     if (it.isSplash() || it.isFluidContainer()) {
  4261.         subType = clientFluidToServer(count);
  4262.     } else {
  4263.         subType = count;
  4264.     }
  4265.  
  4266.     if (!player->hasShopItemForSale(it.id, subType)) {
  4267.         return;
  4268.     }
  4269.  
  4270.     merchant->onPlayerTrade(player, onBuy, it.id, subType, amount, ignoreCap, inBackpacks);
  4271. }
  4272.  
  4273. void Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreEquipped)
  4274. {
  4275.     if (amount == 0 || amount > 100) {
  4276.         return;
  4277.     }
  4278.  
  4279.     Player* player = getPlayerByID(playerId);
  4280.     if (!player) {
  4281.         return;
  4282.     }
  4283.  
  4284.     int32_t onBuy, onSell;
  4285.  
  4286.     Npc* merchant = player->getShopOwner(onBuy, onSell);
  4287.     if (!merchant) {
  4288.         return;
  4289.     }
  4290.  
  4291.     const ItemType& it = Item::items.getItemIdByClientId(spriteId);
  4292.     if (it.id == 0) {
  4293.         return;
  4294.     }
  4295.  
  4296.     uint8_t subType;
  4297.     if (it.isSplash() || it.isFluidContainer()) {
  4298.         subType = clientFluidToServer(count);
  4299.     } else {
  4300.         subType = count;
  4301.     }
  4302.  
  4303.     merchant->onPlayerTrade(player, onSell, it.id, subType, amount, ignoreEquipped);
  4304. }
  4305.  
  4306. void Game::playerCloseShop(uint32_t playerId)
  4307. {
  4308.     Player* player = getPlayerByID(playerId);
  4309.     if (!player) {
  4310.         return;
  4311.     }
  4312.  
  4313.     player->closeShopWindow();
  4314. }
  4315.  
  4316. void Game::playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count)
  4317. {
  4318.     Player* player = getPlayerByID(playerId);
  4319.     if (!player) {
  4320.         return;
  4321.     }
  4322.  
  4323.     int32_t onBuy, onSell;
  4324.  
  4325.     Npc* merchant = player->getShopOwner(onBuy, onSell);
  4326.     if (!merchant) {
  4327.         return;
  4328.     }
  4329.  
  4330.     const ItemType& it = Item::items.getItemIdByClientId(spriteId);
  4331.     if (it.id == 0) {
  4332.         return;
  4333.     }
  4334.  
  4335.     int32_t subType;
  4336.     if (it.isFluidContainer() || it.isSplash()) {
  4337.         subType = clientFluidToServer(count);
  4338.     } else {
  4339.         subType = count;
  4340.     }
  4341.  
  4342.     if (!player->hasShopItemForSale(it.id, subType)) {
  4343.         return;
  4344.     }
  4345.  
  4346.     if (!g_events->eventPlayerOnLookInShop(player, &it, subType)) {
  4347.         return;
  4348.     }
  4349.  
  4350.     std::ostringstream ss;
  4351.     ss << "You see " << Item::getDescription(it, 1, nullptr, subType);
  4352.     player->sendTextMessage(MESSAGE_LOOK, ss.str());
  4353. }
  4354.  
  4355. void Game::playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos)
  4356. {
  4357.     Player* player = getPlayerByID(playerId);
  4358.     if (!player) {
  4359.         return;
  4360.     }
  4361.  
  4362.     Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_LOOK);
  4363.     if (!thing) {
  4364.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  4365.         return;
  4366.     }
  4367.  
  4368.     Position thingPos = thing->getPosition();
  4369.     if (!player->canSee(thingPos)) {
  4370.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  4371.         return;
  4372.     }
  4373.  
  4374.     Position playerPos = player->getPosition();
  4375.  
  4376.     int32_t lookDistance;
  4377.     if (thing != player) {
  4378.         lookDistance = std::max<int32_t>(Position::getDistanceX(playerPos, thingPos), Position::getDistanceY(playerPos, thingPos));
  4379.         if (playerPos.z != thingPos.z) {
  4380.             lookDistance += 15;
  4381.         }
  4382.     } else {
  4383.         lookDistance = -1;
  4384.     }
  4385.  
  4386.     g_events->eventPlayerOnLook(player, pos, thing, stackPos, lookDistance);
  4387. }
  4388.  
  4389. void Game::playerLookInBattleList(uint32_t playerId, uint32_t creatureId)
  4390. {
  4391.     Player* player = getPlayerByID(playerId);
  4392.     if (!player) {
  4393.         return;
  4394.     }
  4395.  
  4396.     Creature* creature = getCreatureByID(creatureId);
  4397.     if (!creature) {
  4398.         return;
  4399.     }
  4400.  
  4401.     if (!player->canSeeCreature(creature)) {
  4402.         return;
  4403.     }
  4404.  
  4405.     const Position& creaturePos = creature->getPosition();
  4406.     if (!player->canSee(creaturePos)) {
  4407.         return;
  4408.     }
  4409.  
  4410.     int32_t lookDistance;
  4411.     if (creature != player) {
  4412.         const Position& playerPos = player->getPosition();
  4413.         lookDistance = std::max<int32_t>(Position::getDistanceX(playerPos, creaturePos), Position::getDistanceY(playerPos, creaturePos));
  4414.         if (playerPos.z != creaturePos.z) {
  4415.             lookDistance += 15;
  4416.         }
  4417.     } else {
  4418.         lookDistance = -1;
  4419.     }
  4420.  
  4421.     g_events->eventPlayerOnLookInBattleList(player, creature, lookDistance);
  4422. }
  4423.  
  4424. void Game::playerQuickLoot(uint32_t playerId, const Position& pos, uint16_t spriteId, uint8_t stackPos, Item* defaultItem)
  4425. {
  4426.     Player* player = getPlayerByID(playerId);
  4427.     if (!player) {
  4428.         return;
  4429.     }
  4430.  
  4431.     if (!player->canDoAction()) {
  4432.         uint32_t delay = player->getNextActionTime();
  4433.         SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerQuickLoot,
  4434.                                                                    this, player->getID(), pos, spriteId, stackPos, defaultItem));
  4435.         player->setNextActionTask(task);
  4436.         return;
  4437.     }
  4438.  
  4439.     if (pos.x != 0xffff) {
  4440.         if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
  4441.             //need to walk to the corpse first before looting it
  4442.             std::forward_list<Direction> listDir;
  4443.             if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
  4444.                 g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
  4445.                 SchedulerTask* task = createSchedulerTask(0, std::bind(&Game::playerQuickLoot,
  4446.                                                                        this, player->getID(), pos, spriteId, stackPos, defaultItem));
  4447.                 player->setNextWalkActionTask(task);
  4448.             } else {
  4449.                 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  4450.             }
  4451.  
  4452.             return;
  4453.         }
  4454.     } else if (!player->isPremium()) {
  4455.         player->sendCancelMessage("You must be premium.");
  4456.         return;
  4457.     }
  4458.  
  4459.     player->setNextActionTask(nullptr);
  4460.  
  4461.     Item* item = nullptr;
  4462.     if (!defaultItem) {
  4463.         Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_FIND_THING);
  4464.         if (!thing) {
  4465.             player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  4466.             return;
  4467.         }
  4468.  
  4469.         item = thing->getItem();
  4470.     } else {
  4471.         item = defaultItem;
  4472.     }
  4473.  
  4474.     if (!item || !item->getParent()) {
  4475.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  4476.         return;
  4477.     }
  4478.  
  4479.     Container* corpse = nullptr;
  4480.     if (pos.x == 0xffff) {
  4481.         corpse = item->getParent()->getContainer();
  4482.     } else {
  4483.         corpse = item->getContainer();
  4484.     }
  4485.  
  4486.     if (!corpse || corpse->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) || corpse->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) {
  4487.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  4488.         return;
  4489.     }
  4490.  
  4491.     if (!corpse->isRewardCorpse()) {
  4492.         uint32_t corpseOwner = corpse->getCorpseOwner();
  4493.         if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) {
  4494.             player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  4495.             return;
  4496.         }
  4497.     }
  4498.  
  4499.     if (pos.x == 0xffff) {
  4500.         uint32_t worth = item->getWorth();
  4501.         ObjectCategory_t category = getObjectCategory(item);
  4502.         ReturnValue ret = internalQuickLootItem(player, item, category);
  4503.  
  4504.         std::stringstream ss;
  4505.         if (ret == RETURNVALUE_NOTENOUGHCAPACITY) {
  4506.             ss << "Attention! The loot you are trying to pick up is too heavy for you to carry.";
  4507.         } else if (ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) {
  4508.             ss << "Attention! The container for " << getObjectCategoryName(category) << " is full.";
  4509.         } else {
  4510.             if (ret == RETURNVALUE_NOERROR) {
  4511.                 player->sendLootStats(item, item->getItemCount());
  4512.                 ss << "You looted ";
  4513.             } else {
  4514.                 ss << "You could not loot ";
  4515.             }
  4516.  
  4517.             if (worth != 0) {
  4518.                 ss << worth << " gold.";
  4519.             } else {
  4520.                 ss << "1 item.";
  4521.             }
  4522.  
  4523.             player->sendTextMessage(MESSAGE_LOOT, ss.str());
  4524.             return;
  4525.         }
  4526.  
  4527.         if (player->lastQuickLootNotification + 15000 < OTSYS_TIME()) {
  4528.             player->sendTextMessage(MESSAGE_GAME_HIGHLIGHT, ss.str());
  4529.         } else {
  4530.             player->sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str());
  4531.         }
  4532.  
  4533.         player->lastQuickLootNotification = OTSYS_TIME();
  4534.     } else {
  4535.         if (corpse->isRewardCorpse()) {
  4536.             g_actions->useItem(player, pos, 0, corpse, false);
  4537.         } else {
  4538.             internalQuickLootCorpse(player, corpse);
  4539.         }
  4540.     }
  4541.  
  4542.     return;
  4543. }
  4544.  
  4545. void Game::playerSetLootContainer(uint32_t playerId, ObjectCategory_t category, const Position& pos, uint16_t spriteId, uint8_t stackPos)
  4546. {
  4547.     Player* player = getPlayerByID(playerId);
  4548.     if (!player || pos.x != 0xffff) {
  4549.         return;
  4550.     }
  4551.  
  4552.     Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USEITEM);
  4553.     if (!thing) {
  4554.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  4555.         return;
  4556.     }
  4557.  
  4558.     Container* container = thing->getContainer();
  4559.     if (!container || (container->getID() == ITEM_GOLD_POUCH && category != OBJECTCATEGORY_GOLD)) {
  4560.         player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  4561.         return;
  4562.     }
  4563.  
  4564.     if (container->getHoldingPlayer() != player) {
  4565.         player->sendCancelMessage("You must be holding the container to set it as a loot container.");
  4566.         return;
  4567.     }
  4568.  
  4569.     Container* previousContainer = player->setLootContainer(category, container);
  4570.     player->sendLootContainers();
  4571.  
  4572.     Cylinder* parent = container->getParent();
  4573.     if (parent) {
  4574.         parent->updateThing(container, container->getID(), container->getItemCount());
  4575.     }
  4576.  
  4577.     if (previousContainer != nullptr) {
  4578.         parent = previousContainer->getParent();
  4579.         if (parent) {
  4580.             parent->updateThing(previousContainer, previousContainer->getID(), previousContainer->getItemCount());
  4581.         }
  4582.     }
  4583. }
  4584.  
  4585. void Game::playerClearLootContainer(uint32_t playerId, ObjectCategory_t category)
  4586. {
  4587.     Player* player = getPlayerByID(playerId);
  4588.     if (!player) {
  4589.         return;
  4590.     }
  4591.  
  4592.     Container* previousContainer = player->setLootContainer(category, nullptr);
  4593.     player->sendLootContainers();
  4594.  
  4595.     if (previousContainer != nullptr) {
  4596.         Cylinder* parent = previousContainer->getParent();
  4597.         if (parent) {
  4598.             parent->updateThing(previousContainer, previousContainer->getID(), previousContainer->getItemCount());
  4599.         }
  4600.     }
  4601. }
  4602.  
  4603. void Game::playerOpenLootContainer(uint32_t playerId, ObjectCategory_t category)
  4604. {
  4605.   Player* player = getPlayerByID(playerId);
  4606.   if (!player) {
  4607.     return;
  4608.   }
  4609.  
  4610.   Container* container = player->getLootContainer(category);
  4611.   if (!container) {
  4612.     return;
  4613.   }
  4614.  
  4615.   player->sendContainer(container->getClientID(), container, container->hasParent(), 0);
  4616. }
  4617.  
  4618.  
  4619. void Game::playerSetQuickLootFallback(uint32_t playerId, bool fallback)
  4620. {
  4621.     Player* player = getPlayerByID(playerId);
  4622.     if (!player) {
  4623.         return;
  4624.     }
  4625.  
  4626.     player->quickLootFallbackToMainContainer = fallback;
  4627. }
  4628.  
  4629. void Game::playerQuickLootBlackWhitelist(uint32_t playerId, QuickLootFilter_t filter, std::vector<uint16_t> clientIds)
  4630. {
  4631.     Player* player = getPlayerByID(playerId);
  4632.     if (!player) {
  4633.         return;
  4634.     }
  4635.  
  4636.     player->quickLootFilter = filter;
  4637.     player->quickLootListClientIds = clientIds;
  4638. }
  4639.  
  4640. void Game::playerRequestLockFind(uint32_t playerId)
  4641. {
  4642.     Player* player = getPlayerByID(playerId);
  4643.     if (!player) {
  4644.         return;
  4645.     }
  4646.  
  4647.     std::map<uint16_t, uint16_t> itemMap;
  4648.     uint16_t count = 0;
  4649.     DepotLocker* depotLocker = player->getDepotLocker(player->getLastDepotId());
  4650.     if (!depotLocker) {
  4651.         return;
  4652.     }
  4653.  
  4654.     for (Item* locker : depotLocker->getItemList()) {
  4655.         Container* c = locker->getContainer();
  4656.         if (c && c->empty()) {
  4657.             continue;
  4658.         }
  4659.  
  4660.         if (c) {
  4661.             for (ContainerIterator it = c->iterator(); it.hasNext(); it.advance()) {
  4662.                 auto itt = itemMap.find((*it)->getID());
  4663.                 if (itt == itemMap.end()) {
  4664.                     itemMap[(*it)->getID()] = Item::countByType((*it), -1);
  4665.                     count++;
  4666.                 } else {
  4667.                     itemMap[(*it)->getID()] += Item::countByType((*it), -1);
  4668.                 }
  4669.             }
  4670.         }
  4671.     }
  4672.  
  4673.     player->sendLockerItems(itemMap, count);
  4674.     return;
  4675. }
  4676.  
  4677. void Game::playerCancelAttackAndFollow(uint32_t playerId)
  4678. {
  4679.     Player* player = getPlayerByID(playerId);
  4680.     if (!player) {
  4681.         return;
  4682.     }
  4683.  
  4684.     playerSetAttackedCreature(playerId, 0);
  4685.     playerFollowCreature(playerId, 0);
  4686.     player->stopWalk();
  4687. }
  4688.  
  4689. void Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId)
  4690. {
  4691.     Player* player = getPlayerByID(playerId);
  4692.     if (!player) {
  4693.         return;
  4694.     }
  4695.  
  4696.     if (player->getAttackedCreature() && creatureId == 0) {
  4697.         player->setAttackedCreature(nullptr);
  4698.         player->sendCancelTarget();
  4699.         return;
  4700.     }
  4701.  
  4702.     Creature* attackCreature = getCreatureByID(creatureId);
  4703.     if (!attackCreature) {
  4704.         player->setAttackedCreature(nullptr);
  4705.         player->sendCancelTarget();
  4706.         return;
  4707.     }
  4708.  
  4709.     ReturnValue ret = Combat::canTargetCreature(player, attackCreature);
  4710.     if (ret != RETURNVALUE_NOERROR) {
  4711.         player->sendCancelMessage(ret);
  4712.         player->sendCancelTarget();
  4713.         player->setAttackedCreature(nullptr);
  4714.         return;
  4715.     }
  4716.  
  4717.     player->setAttackedCreature(attackCreature);
  4718.     g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID())));
  4719. }
  4720.  
  4721. void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId)
  4722. {
  4723.     Player* player = getPlayerByID(playerId);
  4724.     if (!player) {
  4725.         return;
  4726.     }
  4727.  
  4728.     player->setAttackedCreature(nullptr);
  4729.     g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID())));
  4730.     player->setFollowCreature(getCreatureByID(creatureId));
  4731. }
  4732.  
  4733. void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, bool chaseMode, bool secureMode)
  4734. {
  4735.     Player* player = getPlayerByID(playerId);
  4736.     if (!player) {
  4737.         return;
  4738.     }
  4739.  
  4740.     player->setFightMode(fightMode);
  4741.     player->setChaseMode(chaseMode);
  4742.     player->setSecureMode(secureMode);
  4743. }
  4744.  
  4745. void Game::playerRequestAddVip(uint32_t playerId, const std::string& name)
  4746. {
  4747.     if (name.length() > 25) {
  4748.         return;
  4749.     }
  4750.  
  4751.     Player* player = getPlayerByID(playerId);
  4752.     if (!player) {
  4753.         return;
  4754.     }
  4755.  
  4756.     Player* vipPlayer = getPlayerByName(name);
  4757.     if (!vipPlayer) {
  4758.         uint32_t guid;
  4759.         bool specialVip;
  4760.         std::string formattedName = name;
  4761.         if (!IOLoginData::getGuidByNameEx(guid, specialVip, formattedName)) {
  4762.             player->sendTextMessage(MESSAGE_FAILURE, "A player with this name does not exist.");
  4763.             return;
  4764.         }
  4765.  
  4766.         if (specialVip && !player->hasFlag(PlayerFlag_SpecialVIP)) {
  4767.             player->sendTextMessage(MESSAGE_FAILURE, "You can not add this player.");
  4768.             return;
  4769.         }
  4770.  
  4771.         player->addVIP(guid, formattedName, VIPSTATUS_OFFLINE);
  4772.     } else {
  4773.         if (vipPlayer->hasFlag(PlayerFlag_SpecialVIP) && !player->hasFlag(PlayerFlag_SpecialVIP)) {
  4774.             player->sendTextMessage(MESSAGE_FAILURE, "You can not add this player.");
  4775.             return;
  4776.         }
  4777.  
  4778.         if (!vipPlayer->isInGhostMode() || player->isAccessPlayer()) {
  4779.             player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_ONLINE);
  4780.         } else {
  4781.             player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_OFFLINE);
  4782.         }
  4783.     }
  4784. }
  4785.  
  4786. void Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid)
  4787. {
  4788.     Player* player = getPlayerByID(playerId);
  4789.     if (!player) {
  4790.         return;
  4791.     }
  4792.  
  4793.     player->removeVIP(guid);
  4794. }
  4795.  
  4796. void Game::playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify)
  4797. {
  4798.     Player* player = getPlayerByID(playerId);
  4799.     if (!player) {
  4800.         return;
  4801.     }
  4802.  
  4803.     player->editVIP(guid, description, icon, notify);
  4804. }
  4805.  
  4806. void Game::playerApplyImbuement(uint32_t playerId, uint32_t imbuementid, uint8_t slot, bool protectionCharm)
  4807. {
  4808.     Player* player = getPlayerByID(playerId);
  4809.     if (!player) {
  4810.         return;
  4811.     }
  4812.  
  4813.     if (!player->inImbuing()) {
  4814.         return;
  4815.     }
  4816.  
  4817.     Imbuement* imbuement = g_imbuements->getImbuement(imbuementid);
  4818.     if(!imbuement) {
  4819.         return;
  4820.     }
  4821.  
  4822.     Item* item = player->imbuing;
  4823.     if(item == nullptr) {
  4824.         return;
  4825.     }
  4826.  
  4827.   if (item->getTopParent() != player || item->getParent() == player) {
  4828.     return;
  4829.   }
  4830.  
  4831.     g_events->eventPlayerOnApplyImbuement(player, imbuement, item, slot, protectionCharm);
  4832. }
  4833.  
  4834. void Game::playerClearingImbuement(uint32_t playerid, uint8_t slot)
  4835. {
  4836.     Player* player = getPlayerByID(playerid);
  4837.     if (!player) {
  4838.         return;
  4839.     }
  4840.  
  4841.     if (!player->inImbuing()) {
  4842.         return;
  4843.     }
  4844.  
  4845.     Item* item = player->imbuing;
  4846.     if(item == nullptr) {
  4847.         return;
  4848.     }
  4849.  
  4850.     g_events->eventPlayerClearImbuement(player, item, slot);
  4851.     return;
  4852. }
  4853.  
  4854. void Game::playerCloseImbuingWindow(uint32_t playerid)
  4855. {
  4856.     Player* player = getPlayerByID(playerid);
  4857.     if (!player) {
  4858.         return;
  4859.     }
  4860.  
  4861.     player->inImbuing(nullptr);
  4862.     return;
  4863. }
  4864.  
  4865. void Game::playerTurn(uint32_t playerId, Direction dir)
  4866. {
  4867.     Player* player = getPlayerByID(playerId);
  4868.     if (!player) {
  4869.         return;
  4870.     }
  4871.  
  4872.     if (!g_events->eventPlayerOnTurn(player, dir)) {
  4873.         return;
  4874.     }
  4875.  
  4876.     player->resetIdleTime();
  4877.     internalCreatureTurn(player, dir);
  4878. }
  4879.  
  4880. void Game::playerRequestOutfit(uint32_t playerId)
  4881. {
  4882.     if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) {
  4883.         return;
  4884.     }
  4885.  
  4886.     Player* player = getPlayerByID(playerId);
  4887.     if (!player) {
  4888.         return;
  4889.     }
  4890.  
  4891.     player->sendOutfitWindow();
  4892. }
  4893.  
  4894. void Game::playerToggleMount(uint32_t playerId, bool mount)
  4895. {
  4896.     Player* player = getPlayerByID(playerId);
  4897.     if (!player) {
  4898.         return;
  4899.     }
  4900.  
  4901.     player->toggleMount(mount);
  4902. }
  4903.  
  4904. void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
  4905. {
  4906.     if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) {
  4907.         return;
  4908.     }
  4909.  
  4910.     Player* player = getPlayerByID(playerId);
  4911.     if (!player) {
  4912.         return;
  4913.     }
  4914.  
  4915.     const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType);
  4916.     if (!playerOutfit) {
  4917.         outfit.lookMount = 0;
  4918.     }
  4919.  
  4920.     if (outfit.lookMount != 0) {
  4921.         Mount* mount = mounts.getMountByClientID(outfit.lookMount);
  4922.         if (!mount) {
  4923.             return;
  4924.         }
  4925.  
  4926.         if (!player->hasMount(mount)) {
  4927.             return;
  4928.         }
  4929.  
  4930.         if (player->isMounted()) {
  4931.             Mount* prevMount = mounts.getMountByID(player->getCurrentMount());
  4932.             if (prevMount) {
  4933.                 changeSpeed(player, mount->speed - prevMount->speed);
  4934.             }
  4935.  
  4936.             player->setCurrentMount(mount->id);
  4937.         } else {
  4938.             player->setCurrentMount(mount->id);
  4939.             outfit.lookMount = 0;
  4940.         }
  4941.     } else if (player->isMounted()) {
  4942.         player->dismount();
  4943.     }
  4944.  
  4945.     if (player->canWear(outfit.lookType, outfit.lookAddons)) {
  4946.         player->defaultOutfit = outfit;
  4947.  
  4948.         if (player->hasCondition(CONDITION_OUTFIT)) {
  4949.             return;
  4950.         }
  4951.  
  4952.         internalCreatureChangeOutfit(player, outfit);
  4953.     }
  4954. }
  4955.  
  4956. void Game::playerShowQuestLog(uint32_t playerId)
  4957. {
  4958.     Player* player = getPlayerByID(playerId);
  4959.     if (!player) {
  4960.         return;
  4961.     }
  4962.  
  4963.     g_events->eventPlayerOnRequestQuestLog(player);
  4964. }
  4965.  
  4966. void Game::playerShowQuestLine(uint32_t playerId, uint16_t questId)
  4967. {
  4968.     Player* player = getPlayerByID(playerId);
  4969.     if (!player) {
  4970.         return;
  4971.     }
  4972.  
  4973.     g_events->eventPlayerOnRequestQuestLine(player, questId);
  4974. }
  4975.  
  4976. void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type,
  4977.                      const std::string& receiver, const std::string& text)
  4978. {
  4979.     Player* player = getPlayerByID(playerId);
  4980.     if (!player) {
  4981.         return;
  4982.     }
  4983.  
  4984.     player->resetIdleTime();
  4985.  
  4986.     if (playerSaySpell(player, type, text)) {
  4987.         return;
  4988.     }
  4989.  
  4990.     uint32_t muteTime = player->isMuted();
  4991.     if (muteTime > 0) {
  4992.         std::ostringstream ss;
  4993.         ss << "You are still muted for " << muteTime << " seconds.";
  4994.         player->sendTextMessage(MESSAGE_FAILURE, ss.str());
  4995.         return;
  4996.     }
  4997.  
  4998.  
  4999.     if (!text.empty() && text.front() == '/' && player->isAccessPlayer()) {
  5000.         return;
  5001.     }
  5002.  
  5003.     if (type != TALKTYPE_PRIVATE_PN) {
  5004.         player->removeMessageBuffer();
  5005.     }
  5006.  
  5007.     switch (type) {
  5008.         case TALKTYPE_SAY:
  5009.             internalCreatureSay(player, TALKTYPE_SAY, text, false);
  5010.             break;
  5011.  
  5012.         case TALKTYPE_WHISPER:
  5013.             playerWhisper(player, text);
  5014.             break;
  5015.  
  5016.         case TALKTYPE_YELL:
  5017.             playerYell(player, text);
  5018.             break;
  5019.  
  5020.         case TALKTYPE_PRIVATE_TO:
  5021.         case TALKTYPE_PRIVATE_RED_TO:
  5022.             playerSpeakTo(player, type, receiver, text);
  5023.             break;
  5024.  
  5025.         case TALKTYPE_CHANNEL_O:
  5026.         case TALKTYPE_CHANNEL_Y:
  5027.         case TALKTYPE_CHANNEL_R1:
  5028.             g_chat->talkToChannel(*player, type, text, channelId);
  5029.             break;
  5030.  
  5031.         case TALKTYPE_PRIVATE_PN:
  5032.             playerSpeakToNpc(player, text);
  5033.             break;
  5034.  
  5035.         case TALKTYPE_BROADCAST:
  5036.             playerBroadcastMessage(player, text);
  5037.             break;
  5038.  
  5039.         default:
  5040.             break;
  5041.     }
  5042. }
  5043.  
  5044. bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& text)
  5045. {
  5046.     if (player->walkExhausted()) {
  5047.         return true;
  5048.     }
  5049.  
  5050.     std::string words = text;
  5051.     TalkActionResult_t result = g_talkActions->playerSaySpell(player, type, words);
  5052.     if (result == TALKACTION_BREAK) {
  5053.         return true;
  5054.     }
  5055.  
  5056.     result = g_spells->playerSaySpell(player, words);
  5057.     if (result == TALKACTION_BREAK) {
  5058.         player->cancelPush();
  5059.  
  5060.         if (!g_config.getBoolean(ConfigManager::EMOTE_SPELLS)) {
  5061.             return internalCreatureSay(player, TALKTYPE_SPELL_USE, words, false);
  5062.         } else {
  5063.             return internalCreatureSay(player, TALKTYPE_MONSTER_SAY, words, false);
  5064.         }
  5065.  
  5066.     } else if (result == TALKACTION_FAILED) {
  5067.         return true;
  5068.     }
  5069.  
  5070.     return false;
  5071. }
  5072.  
  5073. void Game::playerWhisper(Player* player, const std::string& text)
  5074. {
  5075.     SpectatorHashSet spectators;
  5076.     map.getSpectators(spectators, player->getPosition(), false, false,
  5077.                   Map::maxClientViewportX, Map::maxClientViewportX,
  5078.                   Map::maxClientViewportY, Map::maxClientViewportY);
  5079.  
  5080.     //send to client
  5081.     for (Creature* spectator : spectators) {
  5082.         if (Player* spectatorPlayer = spectator->getPlayer()) {
  5083.             if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) {
  5084.                 spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps");
  5085.             } else {
  5086.                 spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, text);
  5087.             }
  5088.         }
  5089.     }
  5090.  
  5091.     //event method
  5092.     for (Creature* spectator : spectators) {
  5093.         spectator->onCreatureSay(player, TALKTYPE_WHISPER, text);
  5094.     }
  5095. }
  5096.  
  5097. bool Game::playerYell(Player* player, const std::string& text)
  5098. {
  5099.     if (player->getLevel() == 1) {
  5100.         player->sendTextMessage(MESSAGE_FAILURE, "You may not yell as long as you are on level 1.");
  5101.         return false;
  5102.     }
  5103.  
  5104.     if (player->hasCondition(CONDITION_YELLTICKS)) {
  5105.         player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
  5106.         return false;
  5107.     }
  5108.  
  5109.     if (player->getAccountType() < account::AccountType::ACCOUNT_TYPE_GAMEMASTER) {
  5110.         Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_YELLTICKS, 30000, 0);
  5111.         player->addCondition(condition);
  5112.     }
  5113.  
  5114.     internalCreatureSay(player, TALKTYPE_YELL, asUpperCaseString(text), false);
  5115.     return true;
  5116. }
  5117.  
  5118. bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver,
  5119.                          const std::string& text)
  5120. {
  5121.     Player* toPlayer = getPlayerByName(receiver);
  5122.     if (!toPlayer) {
  5123.         player->sendTextMessage(MESSAGE_FAILURE, "A player with this name is not online.");
  5124.         return false;
  5125.     }
  5126.  
  5127.     if (type == TALKTYPE_PRIVATE_RED_TO && (player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() >= account::AccountType::ACCOUNT_TYPE_GAMEMASTER)) {
  5128.         type = TALKTYPE_PRIVATE_RED_FROM;
  5129.     } else {
  5130.         type = TALKTYPE_PRIVATE_FROM;
  5131.     }
  5132.  
  5133.     toPlayer->sendPrivateMessage(player, type, text);
  5134.     toPlayer->onCreatureSay(player, type, text);
  5135.  
  5136.     if (toPlayer->isInGhostMode() && !player->isAccessPlayer()) {
  5137.         player->sendTextMessage(MESSAGE_FAILURE, "A player with this name is not online.");
  5138.     } else {
  5139.         std::ostringstream ss;
  5140.         ss << "Message sent to " << toPlayer->getName() << '.';
  5141.         player->sendTextMessage(MESSAGE_FAILURE, ss.str());
  5142.     }
  5143.     return true;
  5144. }
  5145.  
  5146. void Game::playerSpeakToNpc(Player* player, const std::string& text)
  5147. {
  5148.     SpectatorHashSet spectators;
  5149.     map.getSpectators(spectators, player->getPosition());
  5150.     for (Creature* spectator : spectators) {
  5151.         if (spectator->getNpc()) {
  5152.             spectator->onCreatureSay(player, TALKTYPE_PRIVATE_PN, text);
  5153.         }
  5154.     }
  5155. }
  5156.  
  5157. //--
  5158. bool Game::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/,
  5159.                             int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const
  5160. {
  5161.     return map.canThrowObjectTo(fromPos, toPos, checkLineOfSight, rangex, rangey);
  5162. }
  5163.  
  5164. bool Game::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const
  5165. {
  5166.     return map.isSightClear(fromPos, toPos, floorCheck);
  5167. }
  5168.  
  5169. bool Game::internalCreatureTurn(Creature* creature, Direction dir)
  5170. {
  5171.     if (creature->getDirection() == dir) {
  5172.         return false;
  5173.     }
  5174.  
  5175.     if (Player* player = creature->getPlayer()) {
  5176.         player->cancelPush();
  5177.     }
  5178.     creature->setDirection(dir);
  5179.  
  5180.     //send to client
  5181.     SpectatorHashSet spectators;
  5182.     map.getSpectators(spectators, creature->getPosition(), true, true);
  5183.     for (Creature* spectator : spectators) {
  5184.         spectator->getPlayer()->sendCreatureTurn(creature);
  5185.     }
  5186.     return true;
  5187. }
  5188.  
  5189. bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text,
  5190.                                bool ghostMode, SpectatorHashSet* spectatorsPtr/* = nullptr*/, const Position* pos/* = nullptr*/)
  5191. {
  5192.     if (text.empty()) {
  5193.         return false;
  5194.     }
  5195.  
  5196.     if (!pos) {
  5197.         pos = &creature->getPosition();
  5198.     }
  5199.  
  5200.     SpectatorHashSet spectators;
  5201.  
  5202.     if (!spectatorsPtr || spectatorsPtr->empty()) {
  5203.         // This somewhat complex construct ensures that the cached SpectatorHashSet
  5204.         // is used if available and if it can be used, else a local vector is
  5205.         // used (hopefully the compiler will optimize away the construction of
  5206.         // the temporary when it's not used).
  5207.         if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) {
  5208.             map.getSpectators(spectators, *pos, false, false,
  5209.                           Map::maxClientViewportX, Map::maxClientViewportX,
  5210.                           Map::maxClientViewportY, Map::maxClientViewportY);
  5211.         } else {
  5212.             map.getSpectators(spectators, *pos, true, false, 18, 18, 14, 14);
  5213.         }
  5214.     } else {
  5215.         spectators = (*spectatorsPtr);
  5216.     }
  5217.  
  5218.     //send to client
  5219.     for (Creature* spectator : spectators) {
  5220.         if (Player* tmpPlayer = spectator->getPlayer()) {
  5221.             if (!ghostMode || tmpPlayer->canSeeCreature(creature)) {
  5222.                 tmpPlayer->sendCreatureSay(creature, type, text, pos);
  5223.             }
  5224.         }
  5225.     }
  5226.  
  5227.     //event method
  5228.     for (Creature* spectator : spectators) {
  5229.         spectator->onCreatureSay(creature, type, text);
  5230.         if (creature != spectator) {
  5231.             g_events->eventCreatureOnHear(spectator, creature, text, type);
  5232.         }
  5233.     }
  5234.     return true;
  5235. }
  5236.  
  5237. void Game::checkCreatureWalk(uint32_t creatureId)
  5238. {
  5239.     Creature* creature = getCreatureByID(creatureId);
  5240.     if (creature && creature->getHealth() > 0) {
  5241.         creature->onCreatureWalk();
  5242.         cleanup();
  5243.     }
  5244. }
  5245.  
  5246. void Game::updateCreatureWalk(uint32_t creatureId)
  5247. {
  5248.     Creature* creature = getCreatureByID(creatureId);
  5249.     if (creature && creature->getHealth() > 0) {
  5250.         creature->goToFollowCreature();
  5251.     }
  5252. }
  5253.  
  5254. void Game::checkCreatureAttack(uint32_t creatureId)
  5255. {
  5256.     Creature* creature = getCreatureByID(creatureId);
  5257.     if (creature && creature->getHealth() > 0) {
  5258.         creature->onAttacking(0);
  5259.     }
  5260. }
  5261.  
  5262. void Game::addCreatureCheck(Creature* creature)
  5263. {
  5264.     creature->creatureCheck = true;
  5265.  
  5266.     if (creature->inCheckCreaturesVector) {
  5267.         // already in a vector
  5268.         return;
  5269.     }
  5270.  
  5271.     creature->inCheckCreaturesVector = true;
  5272.     checkCreatureLists[uniform_random(0, EVENT_CREATURECOUNT - 1)].push_back(creature);
  5273.     creature->incrementReferenceCounter();
  5274. }
  5275.  
  5276. void Game::removeCreatureCheck(Creature* creature)
  5277. {
  5278.     if (creature->inCheckCreaturesVector) {
  5279.         creature->creatureCheck = false;
  5280.     }
  5281. }
  5282.  
  5283. void Game::checkCreatures(size_t index)
  5284. {
  5285.     g_scheduler.addEvent(createSchedulerTask(EVENT_CHECK_CREATURE_INTERVAL, std::bind(&Game::checkCreatures, this, (index + 1) % EVENT_CREATURECOUNT)));
  5286.  
  5287.     auto& checkCreatureList = checkCreatureLists[index];
  5288.     auto it = checkCreatureList.begin(), end = checkCreatureList.end();
  5289.     while (it != end) {
  5290.         Creature* creature = *it;
  5291.         if (creature->creatureCheck) {
  5292.             if (creature->getHealth() > 0) {
  5293.                 creature->onThink(EVENT_CREATURE_THINK_INTERVAL);
  5294.                 creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL);
  5295.                 creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL);
  5296.             }
  5297.             ++it;
  5298.         } else {
  5299.             creature->inCheckCreaturesVector = false;
  5300.             it = checkCreatureList.erase(it);
  5301.             ReleaseCreature(creature);
  5302.         }
  5303.     }
  5304.  
  5305.     cleanup();
  5306. }
  5307.  
  5308. void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta)
  5309. {
  5310.     int32_t varSpeed = creature->getSpeed() - creature->getBaseSpeed();
  5311.     varSpeed += varSpeedDelta;
  5312.  
  5313.     creature->setSpeed(varSpeed);
  5314.  
  5315.     //send to clients
  5316.     SpectatorHashSet spectators;
  5317.     map.getSpectators(spectators, creature->getPosition(), false, true);
  5318.     for (Creature* spectator : spectators) {
  5319.         spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed());
  5320.     }
  5321. }
  5322.  
  5323. void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outfit)
  5324. {
  5325.     if (!g_events->eventCreatureOnChangeOutfit(creature, outfit)) {
  5326.         return;
  5327.     }
  5328.  
  5329.     creature->setCurrentOutfit(outfit);
  5330.  
  5331.     if (creature->isInvisible()) {
  5332.         return;
  5333.     }
  5334.  
  5335.     //send to clients
  5336.     SpectatorHashSet spectators;
  5337.     map.getSpectators(spectators, creature->getPosition(), true, true);
  5338.     for (Creature* spectator : spectators) {
  5339.         spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit);
  5340.     }
  5341. }
  5342.  
  5343. void Game::internalCreatureChangeVisible(Creature* creature, bool visible)
  5344. {
  5345.     //send to clients
  5346.     SpectatorHashSet spectators;
  5347.     map.getSpectators(spectators, creature->getPosition(), true, true);
  5348.     for (Creature* spectator : spectators) {
  5349.         spectator->getPlayer()->sendCreatureChangeVisible(creature, visible);
  5350.     }
  5351. }
  5352.  
  5353. void Game::changeLight(const Creature* creature)
  5354. {
  5355.     //send to clients
  5356.     SpectatorHashSet spectators;
  5357.     map.getSpectators(spectators, creature->getPosition(), true, true);
  5358.     for (Creature* spectator : spectators) {
  5359.         spectator->getPlayer()->sendCreatureLight(creature);
  5360.     }
  5361. }
  5362.  
  5363. void Game::updateCreatureIcon(const Creature* creature)
  5364. {
  5365.     //send to clients
  5366.     SpectatorHashSet spectators;
  5367.     map.getSpectators(spectators, creature->getPosition(), true, true);
  5368.     for (Creature* spectator : spectators) {
  5369.         spectator->getPlayer()->sendCreatureIcon(creature);
  5370.     }
  5371. }
  5372.  
  5373. bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field)
  5374. {
  5375.     if (damage.primary.type == COMBAT_NONE && damage.secondary.type == COMBAT_NONE) {
  5376.         return true;
  5377.     }
  5378.  
  5379.     if (target->getPlayer() && target->isInGhostMode()) {
  5380.         return true;
  5381.     }
  5382.  
  5383.     if (damage.primary.value > 0) {
  5384.         return false;
  5385.     }
  5386.  
  5387.     static const auto sendBlockEffect = [this](BlockType_t blockType, CombatType_t combatType, const Position& targetPos) {
  5388.         if (blockType == BLOCK_DEFENSE) {
  5389.             addMagicEffect(targetPos, CONST_ME_POFF);
  5390.         } else if (blockType == BLOCK_ARMOR) {
  5391.             addMagicEffect(targetPos, CONST_ME_BLOCKHIT);
  5392.         } else if (blockType == BLOCK_IMMUNITY) {
  5393.             uint8_t hitEffect = 0;
  5394.             switch (combatType) {
  5395.                 case COMBAT_UNDEFINEDDAMAGE: {
  5396.                     return;
  5397.                 }
  5398.                 case COMBAT_ENERGYDAMAGE:
  5399.                 case COMBAT_FIREDAMAGE:
  5400.                 case COMBAT_PHYSICALDAMAGE:
  5401.                 case COMBAT_ICEDAMAGE:
  5402.                 case COMBAT_DEATHDAMAGE: {
  5403.                     hitEffect = CONST_ME_BLOCKHIT;
  5404.                     break;
  5405.                 }
  5406.                 case COMBAT_EARTHDAMAGE: {
  5407.                     hitEffect = CONST_ME_GREEN_RINGS;
  5408.                     break;
  5409.                 }
  5410.                 case COMBAT_HOLYDAMAGE: {
  5411.                     hitEffect = CONST_ME_HOLYDAMAGE;
  5412.                     break;
  5413.                 }
  5414.                 default: {
  5415.                     hitEffect = CONST_ME_POFF;
  5416.                     break;
  5417.                 }
  5418.             }
  5419.             addMagicEffect(targetPos, hitEffect);
  5420.         }
  5421.     };
  5422.  
  5423.     bool canHeal = false;
  5424.         CombatDamage damageHeal;
  5425.         damageHeal.primary.type = COMBAT_HEALING;
  5426.  
  5427.     bool canReflect = false;
  5428.         CombatDamage damageReflected;
  5429.  
  5430.     BlockType_t primaryBlockType, secondaryBlockType;
  5431.     if (damage.primary.type != COMBAT_NONE) {
  5432.         // Damage reflection primary
  5433.         if (attacker && target->getMonster()) {
  5434.             uint32_t primaryReflect = target->getMonster()->getReflectValue(damage.primary.type);
  5435.             if (primaryReflect > 0) {
  5436.                 damageReflected.primary.type = damage.primary.type;
  5437.                 damageReflected.primary.value = std::ceil((damage.primary.value) * (primaryReflect / 100.));
  5438.                 damageReflected.extension = true;
  5439.                 damageReflected.exString = "(damage reflection)";
  5440.                 canReflect = true;
  5441.             }
  5442.         }
  5443.         damage.primary.value = -damage.primary.value;
  5444.         // Damage healing primary
  5445.         if (attacker && target->getMonster()) {
  5446.             uint32_t primaryHealing = target->getMonster()->getHealingCombatValue(damage.primary.type);
  5447.             if (primaryHealing > 0) {
  5448.                 damageHeal.primary.value = std::ceil((damage.primary.value) * (primaryHealing / 100.));
  5449.                 canHeal = true;
  5450.             }
  5451.         }
  5452.         primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field);
  5453.  
  5454.         damage.primary.value = -damage.primary.value;
  5455.         sendBlockEffect(primaryBlockType, damage.primary.type, target->getPosition());
  5456.     } else {
  5457.         primaryBlockType = BLOCK_NONE;
  5458.     }
  5459.  
  5460.     if (damage.secondary.type != COMBAT_NONE) {
  5461.         // Damage reflection secondary
  5462.         if (attacker && target->getMonster()) {
  5463.             uint32_t secondaryReflect = target->getMonster()->getReflectValue(damage.secondary.type);
  5464.             if (secondaryReflect > 0) {
  5465.                 if (!canReflect) {
  5466.                     damageReflected.primary.type = damage.secondary.type;
  5467.                     damageReflected.primary.value = std::ceil((damage.secondary.value) * (secondaryReflect / 100.));
  5468.                     damageReflected.extension = true;
  5469.                     damageReflected.exString = "(damage reflection)";
  5470.                     canReflect = true;
  5471.                 } else {
  5472.                     damageReflected.secondary.type = damage.secondary.type;
  5473.                     damageReflected.secondary.value = std::ceil((damage.secondary.value) * (secondaryReflect / 100.));
  5474.                 }
  5475.             }
  5476.         }
  5477.         damage.secondary.value = -damage.secondary.value;
  5478.         // Damage healing secondary
  5479.         if (attacker && target->getMonster()) {
  5480.             uint32_t secondaryHealing = target->getMonster()->getHealingCombatValue(damage.secondary.type);
  5481.             if (secondaryHealing > 0) {;
  5482.                 damageHeal.primary.value += std::ceil((damage.secondary.value) * (secondaryHealing / 100.));
  5483.                 canHeal = true;
  5484.             }
  5485.         }
  5486.         secondaryBlockType = target->blockHit(attacker, damage.secondary.type, damage.secondary.value, false, false, field);
  5487.  
  5488.         damage.secondary.value = -damage.secondary.value;
  5489.         sendBlockEffect(secondaryBlockType, damage.secondary.type, target->getPosition());
  5490.     } else {
  5491.         secondaryBlockType = BLOCK_NONE;
  5492.     }
  5493.     if (canReflect) {
  5494.         combatChangeHealth(target, attacker, damageReflected, false);
  5495.     }
  5496.     if (canHeal) {
  5497.         combatChangeHealth(nullptr, target, damageHeal);
  5498.     }
  5499.     return (primaryBlockType != BLOCK_NONE) && (secondaryBlockType != BLOCK_NONE);
  5500. }
  5501.  
  5502. void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect)
  5503. {
  5504.     switch (combatType) {
  5505.         case COMBAT_PHYSICALDAMAGE: {
  5506.             Item* splash = nullptr;
  5507.             switch (target->getRace()) {
  5508.                 case RACE_VENOM:
  5509.                     color = TEXTCOLOR_LIGHTGREEN;
  5510.                     effect = CONST_ME_HITBYPOISON;
  5511.                     splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_SLIME);
  5512.                     break;
  5513.                 case RACE_BLOOD:
  5514.                     color = TEXTCOLOR_RED;
  5515.                     effect = CONST_ME_DRAWBLOOD;
  5516.                     if (const Tile* tile = target->getTile()) {
  5517.                         if (!tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
  5518.                             splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD);
  5519.                         }
  5520.                     }
  5521.                     break;
  5522.                 case RACE_UNDEAD:
  5523.                     color = TEXTCOLOR_LIGHTGREY;
  5524.                     effect = CONST_ME_HITAREA;
  5525.                     break;
  5526.                 case RACE_FIRE:
  5527.                     color = TEXTCOLOR_ORANGE;
  5528.                     effect = CONST_ME_DRAWBLOOD;
  5529.                     break;
  5530.                 case RACE_ENERGY:
  5531.                     color = TEXTCOLOR_PURPLE;
  5532.                     effect = CONST_ME_ENERGYHIT;
  5533.                     break;
  5534.                 default:
  5535.                     color = TEXTCOLOR_NONE;
  5536.                     effect = CONST_ME_NONE;
  5537.                     break;
  5538.             }
  5539.  
  5540.             if (splash) {
  5541.                 internalAddItem(target->getTile(), splash, INDEX_WHEREEVER, FLAG_NOLIMIT);
  5542.                 startDecay(splash);
  5543.             }
  5544.  
  5545.             break;
  5546.         }
  5547.  
  5548.         case COMBAT_ENERGYDAMAGE: {
  5549.             color = TEXTCOLOR_PURPLE;
  5550.             effect = CONST_ME_ENERGYHIT;
  5551.             break;
  5552.         }
  5553.  
  5554.         case COMBAT_EARTHDAMAGE: {
  5555.             color = TEXTCOLOR_LIGHTGREEN;
  5556.             effect = CONST_ME_GREEN_RINGS;
  5557.             break;
  5558.         }
  5559.  
  5560.         case COMBAT_DROWNDAMAGE: {
  5561.             color = TEXTCOLOR_LIGHTBLUE;
  5562.             effect = CONST_ME_LOSEENERGY;
  5563.             break;
  5564.         }
  5565.         case COMBAT_FIREDAMAGE: {
  5566.             color = TEXTCOLOR_ORANGE;
  5567.             effect = CONST_ME_HITBYFIRE;
  5568.             break;
  5569.         }
  5570.         case COMBAT_ICEDAMAGE: {
  5571.             color = TEXTCOLOR_SKYBLUE;
  5572.             effect = CONST_ME_ICEATTACK;
  5573.             break;
  5574.         }
  5575.         case COMBAT_HOLYDAMAGE: {
  5576.             color = TEXTCOLOR_YELLOW;
  5577.             effect = CONST_ME_HOLYDAMAGE;
  5578.             break;
  5579.         }
  5580.         case COMBAT_DEATHDAMAGE: {
  5581.             color = TEXTCOLOR_DARKRED;
  5582.             effect = CONST_ME_SMALLCLOUDS;
  5583.             break;
  5584.         }
  5585.         case COMBAT_LIFEDRAIN: {
  5586.             color = TEXTCOLOR_RED;
  5587.             effect = CONST_ME_MAGIC_RED;
  5588.             break;
  5589.         }
  5590.         default: {
  5591.             color = TEXTCOLOR_NONE;
  5592.             effect = CONST_ME_NONE;
  5593.             break;
  5594.         }
  5595.     }
  5596. }
  5597.  
  5598. bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage, bool isEvent /*= false*/)
  5599. {
  5600.     using namespace std;
  5601.     const Position& targetPos = target->getPosition();
  5602.     if (damage.primary.value > 0) {
  5603.         if (target->getHealth() <= 0) {
  5604.             return false;
  5605.         }
  5606.  
  5607.         Player* attackerPlayer;
  5608.         if (attacker) {
  5609.             attackerPlayer = attacker->getPlayer();
  5610.         } else {
  5611.             attackerPlayer = nullptr;
  5612.         }
  5613.  
  5614.         Player* targetPlayer = target->getPlayer();
  5615.         if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
  5616.             return false;
  5617.         }
  5618.  
  5619.         if (damage.origin != ORIGIN_NONE) {
  5620.             const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE);
  5621.             if (!events.empty()) {
  5622.                 for (CreatureEvent* creatureEvent : events) {
  5623.                     creatureEvent->executeHealthChange(target, attacker, damage);
  5624.                 }
  5625.                 damage.origin = ORIGIN_NONE;
  5626.                 return combatChangeHealth(attacker, target, damage);
  5627.             }
  5628.         }
  5629.  
  5630.         int32_t realHealthChange = target->getHealth();
  5631.         target->gainHealth(attacker, damage.primary.value);
  5632.         realHealthChange = target->getHealth() - realHealthChange;
  5633.  
  5634.         if (realHealthChange > 0 && !target->isInGhostMode()) {
  5635.             if (targetPlayer) {
  5636.                 targetPlayer->updateImpactTracker(COMBAT_HEALING, realHealthChange);
  5637.             }
  5638.             std::stringstream ss;
  5639.  
  5640.             ss << realHealthChange << (realHealthChange != 1 ? " hitpoints." : " hitpoint.");
  5641.             std::string damageString = ss.str();
  5642.  
  5643.             std::string spectatorMessage;
  5644.  
  5645.             TextMessage message;
  5646.             message.position = targetPos;
  5647.             message.primary.value = realHealthChange;
  5648.             message.primary.color = TEXTCOLOR_PASTELRED;
  5649.  
  5650.             SpectatorHashSet spectators;
  5651.             map.getSpectators(spectators, targetPos, false, true);
  5652.             for (Creature* spectator : spectators) {
  5653.                 Player* tmpPlayer = spectator->getPlayer();
  5654.                 if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
  5655.                     ss.str({});
  5656.                     ss << "You heal " << target->getNameDescription() << " for " << damageString;
  5657.                     message.type = MESSAGE_HEALED;
  5658.                     message.text = ss.str();
  5659.                 } else if (tmpPlayer == targetPlayer) {
  5660.                     ss.str({});
  5661.                     if (!attacker) {
  5662.                         ss << "You were healed";
  5663.                     } else if (targetPlayer == attackerPlayer) {
  5664.                         ss << "You heal yourself";
  5665.                     } else {
  5666.                         ss << "You were healed by " << attacker->getNameDescription();
  5667.                     }
  5668.                     ss << " for " << damageString;
  5669.                     message.type = MESSAGE_HEALED;
  5670.                     message.text = ss.str();
  5671.                 } else {
  5672.                     if (spectatorMessage.empty()) {
  5673.                         ss.str({});
  5674.                         if (!attacker) {
  5675.                             ss << ucfirst(target->getNameDescription()) << " was healed";
  5676.                         } else {
  5677.                             ss << ucfirst(attacker->getNameDescription()) << " healed ";
  5678.                             if (attacker == target) {
  5679.                                 ss << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "herself" : "himself") : "itself");
  5680.                             } else {
  5681.                                 ss << target->getNameDescription();
  5682.                             }
  5683.                         }
  5684.                         ss << " for " << damageString;
  5685.                         spectatorMessage = ss.str();
  5686.                     }
  5687.                     message.type = MESSAGE_HEALED_OTHERS;
  5688.                     message.text = spectatorMessage;
  5689.                 }
  5690.                 tmpPlayer->sendTextMessage(message);
  5691.             }
  5692.         }
  5693.     } else {
  5694.         if (!target->isAttackable()) {
  5695.             if (!target->isInGhostMode()) {
  5696.                 addMagicEffect(targetPos, CONST_ME_POFF);
  5697.             }
  5698.             return true;
  5699.         }
  5700.  
  5701.         Player* attackerPlayer;
  5702.         if (attacker) {
  5703.             attackerPlayer = attacker->getPlayer();
  5704.         } else {
  5705.             attackerPlayer = nullptr;
  5706.         }
  5707.  
  5708.         Player* targetPlayer = target->getPlayer();
  5709.         if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
  5710.             return false;
  5711.         }
  5712.  
  5713.         damage.primary.value = std::abs(damage.primary.value);
  5714.         damage.secondary.value = std::abs(damage.secondary.value);
  5715.  
  5716.         TextMessage message;
  5717.         message.position = targetPos;
  5718.  
  5719.         if (!isEvent) {
  5720.             g_events->eventCreatureOnDrainHealth(target, attacker, damage.primary.type, damage.primary.value, damage.secondary.type, damage.secondary.value, message.primary.color, message.secondary.color);
  5721.         }
  5722.         if (damage.origin != ORIGIN_NONE && attacker && damage.primary.type != COMBAT_HEALING) {
  5723.             damage.primary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.;
  5724.             damage.secondary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.;
  5725.         }
  5726.         if (damage.origin != ORIGIN_NONE && target && damage.primary.type != COMBAT_HEALING) {
  5727.             damage.primary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.;
  5728.             damage.secondary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.;
  5729.         }
  5730.         int32_t healthChange = damage.primary.value + damage.secondary.value;
  5731.         if (healthChange == 0) {
  5732.             return true;
  5733.         }
  5734.  
  5735.         SpectatorHashSet spectators;
  5736.         map.getSpectators(spectators, targetPos, true, true);
  5737.  
  5738.         if (damage.critical) {
  5739.             addMagicEffect(spectators, targetPos, CONST_ME_CRITICAL_DAMAGE);
  5740.         }
  5741.  
  5742.         if (!damage.extension && attacker && attacker->getMonster() && targetPlayer) {
  5743.             // Charm rune (target as player)
  5744.             MonsterType* mType = g_monsters.getMonsterType(attacker->getName());
  5745.             if (mType) {
  5746.                 IOBestiary g_bestiary;
  5747.                 charmRune_t activeCharm = g_bestiary.getCharmFromTarget(targetPlayer, mType);
  5748.                 if (activeCharm != CHARM_NONE && activeCharm != CHARM_CLEANSE) {
  5749.                     Charm* charm = g_bestiary.getBestiaryCharm(activeCharm);
  5750.                     if (charm && charm->type == CHARM_DEFENSIVE &&(charm->chance > normal_random(0, 100))) {
  5751.                         if (g_bestiary.parseCharmCombat(charm, targetPlayer, attacker, (damage.primary.value + damage.secondary.value))) {
  5752.                             return false; // Dodge charm
  5753.                         }
  5754.                     }
  5755.                 }
  5756.             }
  5757.         }
  5758.  
  5759.         if (target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) {
  5760.             int32_t manaDamage = std::min<int32_t>(target->getMana(), healthChange);
  5761.             uint16_t manaShield = target->getManaShield();
  5762.             if (manaShield > 0) {
  5763.                 if (manaShield > manaDamage) {
  5764.                     target->setManaShield(manaShield - manaDamage);
  5765.                     manaShield = manaShield - manaDamage;
  5766.                 } else {
  5767.                     manaDamage = manaShield;
  5768.                     target->removeCondition(CONDITION_MANASHIELD);
  5769.                     manaShield  = 0;
  5770.                 }
  5771.             }
  5772.             if (manaDamage != 0) {
  5773.                 if (damage.origin != ORIGIN_NONE) {
  5774.                     const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
  5775.                     if (!events.empty()) {
  5776.                         for (CreatureEvent* creatureEvent : events) {
  5777.                             creatureEvent->executeManaChange(target, attacker, damage);
  5778.                         }
  5779.                         healthChange = damage.primary.value + damage.secondary.value;
  5780.                         if (healthChange == 0) {
  5781.                             return true;
  5782.                         }
  5783.                         manaDamage = std::min<int32_t>(target->getMana(), healthChange);
  5784.                     }
  5785.                 }
  5786.  
  5787.                 target->drainMana(attacker, manaDamage);
  5788.  
  5789.                 if(target->getMana() == 0 && manaShield > 0) {
  5790.                     target->removeCondition(CONDITION_MANASHIELD);
  5791.                 }
  5792.  
  5793.                 addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY);
  5794.  
  5795.                 std::stringstream ss;
  5796.  
  5797.                 std::string damageString = std::to_string(manaDamage);
  5798.  
  5799.                 std::string spectatorMessage;
  5800.  
  5801.                 message.primary.value = manaDamage;
  5802.                 message.primary.color = TEXTCOLOR_BLUE;
  5803.  
  5804.                 for (Creature* spectator : spectators) {
  5805.                     Player* tmpPlayer = spectator->getPlayer();
  5806.                     if (tmpPlayer->getPosition().z != targetPos.z) {
  5807.                         continue;
  5808.                     }
  5809.  
  5810.                     if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
  5811.                         ss.str({});
  5812.                         ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana due to your attack.";
  5813.                         message.type = MESSAGE_DAMAGE_DEALT;
  5814.                         message.text = ss.str();
  5815.                     } else if (tmpPlayer == targetPlayer) {
  5816.                         ss.str({});
  5817.                         ss << "You lose " << damageString << " mana";
  5818.                         if (!attacker) {
  5819.                             ss << '.';
  5820.                         } else if (targetPlayer == attackerPlayer) {
  5821.                             ss << " due to your own attack.";
  5822.                         } else {
  5823.                             ss << " due to an attack by " << attacker->getNameDescription() << '.';
  5824.                         }
  5825.                         message.type = MESSAGE_DAMAGE_RECEIVED;
  5826.                         message.text = ss.str();
  5827.                     } else {
  5828.                         if (spectatorMessage.empty()) {
  5829.                             ss.str({});
  5830.                             ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana";
  5831.                             if (attacker) {
  5832.                                 ss << " due to ";
  5833.                                 if (attacker == target) {
  5834.                                     ss << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack") : "its own attack");
  5835.                                 } else {
  5836.                                     ss << "an attack by " << attacker->getNameDescription();
  5837.                                 }
  5838.                             }
  5839.                             ss << '.';
  5840.                             spectatorMessage = ss.str();
  5841.                         }
  5842.                         message.type = MESSAGE_DAMAGE_OTHERS;
  5843.                         message.text = spectatorMessage;
  5844.                     }
  5845.                     tmpPlayer->sendTextMessage(message);
  5846.                 }
  5847.  
  5848.                 damage.primary.value -= manaDamage;
  5849.                 if (damage.primary.value < 0) {
  5850.                     damage.secondary.value = std::max<int32_t>(0, damage.secondary.value + damage.primary.value);
  5851.                     damage.primary.value = 0;
  5852.                 }
  5853.             }
  5854.         }
  5855.  
  5856.         int32_t realDamage = damage.primary.value + damage.secondary.value;
  5857.         if (realDamage == 0) {
  5858.             return true;
  5859.         }
  5860.  
  5861.         if (damage.origin != ORIGIN_NONE) {
  5862.             const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE);
  5863.             if (!events.empty()) {
  5864.                 for (CreatureEvent* creatureEvent : events) {
  5865.                     creatureEvent->executeHealthChange(target, attacker, damage);
  5866.                 }
  5867.                 damage.origin = ORIGIN_NONE;
  5868.                 return combatChangeHealth(attacker, target, damage);
  5869.             }
  5870.         }
  5871.  
  5872.         int32_t targetHealth = target->getHealth();
  5873.         if (damage.primary.value >= targetHealth) {
  5874.             damage.primary.value = targetHealth;
  5875.             damage.secondary.value = 0;
  5876.         } else if (damage.secondary.value) {
  5877.             damage.secondary.value = std::min<int32_t>(damage.secondary.value, targetHealth - damage.primary.value);
  5878.         }
  5879.  
  5880.         realDamage = damage.primary.value + damage.secondary.value;
  5881.         if (realDamage == 0) {
  5882.             return true;
  5883.         } else if (realDamage >= targetHealth) {
  5884.             for (CreatureEvent* creatureEvent : target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) {
  5885.                 if (!creatureEvent->executeOnPrepareDeath(target, attacker)) {
  5886.                     return false;
  5887.                 }
  5888.             }
  5889.         }
  5890.  
  5891.         target->drainHealth(attacker, realDamage);
  5892.         if (realDamage > 0) {
  5893.             if (Monster* targetMonster = target->getMonster()) {
  5894.                 if (attackerPlayer && attackerPlayer->getPlayer()) {
  5895.                     attackerPlayer->updateImpactTracker(damage.secondary.type, damage.secondary.value);
  5896.                 }
  5897.  
  5898.                 if (targetMonster->israndomStepping()) {
  5899.                     targetMonster->setIgnoreFieldDamage(true);
  5900.                     targetMonster->updateMapCache();
  5901.                 }
  5902.             }
  5903.         }
  5904.  
  5905.         // Using real damage
  5906.         if (attackerPlayer) {
  5907.             //life leech
  5908.             uint16_t lifeChance = attackerPlayer->getSkillLevel(SKILL_LIFE_LEECH_CHANCE);
  5909.             uint16_t lifeSkill = attackerPlayer->getSkillLevel(SKILL_LIFE_LEECH_AMOUNT);
  5910.             if (normal_random(0, 100) < lifeChance) {
  5911.                 // Vampiric charm rune
  5912.                 if (target && target->getMonster()) {
  5913.                     uint16_t playerCharmRaceidVamp = attackerPlayer->parseRacebyCharm(CHARM_VAMP, false, 0);
  5914.                     if (playerCharmRaceidVamp != 0) {
  5915.                         MonsterType* mType = g_monsters.getMonsterType(target->getName());
  5916.                         if (mType && playerCharmRaceidVamp == mType->info.raceid) {
  5917.                             IOBestiary g_bestiary;
  5918.                             Charm* lifec = g_bestiary.getBestiaryCharm(CHARM_VAMP);
  5919.                             if (lifec) {
  5920.                                 lifeSkill += lifec->percent;
  5921.                             }
  5922.                         }
  5923.                     }
  5924.                 }
  5925.                 CombatParams tmpParams;
  5926.                 CombatDamage tmpDamage;
  5927.  
  5928.                 int affected = damage.affected;
  5929.                 tmpDamage.origin = ORIGIN_SPELL;
  5930.                 tmpDamage.primary.type = COMBAT_HEALING;
  5931.                 tmpDamage.primary.value = std::round(realDamage * (lifeSkill / 100.) * (0.2 * affected + 0.9)) / affected;
  5932.  
  5933.                 Combat::doCombatHealth(nullptr, attackerPlayer, tmpDamage, tmpParams);
  5934.             }
  5935.  
  5936.             //mana leech
  5937.             uint16_t manaChance = attackerPlayer->getSkillLevel(SKILL_MANA_LEECH_CHANCE);
  5938.             uint16_t manaSkill = attackerPlayer->getSkillLevel(SKILL_MANA_LEECH_AMOUNT);
  5939.             if (normal_random(0, 100) < manaChance) {
  5940.                 // Void charm rune
  5941.                 if (target && target->getMonster()) {
  5942.                     uint16_t playerCharmRaceidVoid = attackerPlayer->parseRacebyCharm(CHARM_VOID, false, 0);
  5943.                     if (playerCharmRaceidVoid != 0) {
  5944.                         MonsterType* mType = g_monsters.getMonsterType(target->getName());
  5945.                         if (mType && playerCharmRaceidVoid == mType->info.raceid) {
  5946.                             IOBestiary g_bestiary;
  5947.                             Charm* voidc = g_bestiary.getBestiaryCharm(CHARM_VOID);
  5948.                             if (voidc) {
  5949.                                 manaSkill += voidc->percent;
  5950.                             }
  5951.                         }
  5952.                     }
  5953.                 }
  5954.                 CombatParams tmpParams;
  5955.                 CombatDamage tmpDamage;
  5956.  
  5957.                 int affected = damage.affected;
  5958.                 tmpDamage.origin = ORIGIN_SPELL;
  5959.                 tmpDamage.primary.type = COMBAT_MANADRAIN;
  5960.                 tmpDamage.primary.value = std::round(realDamage * (manaSkill / 100.) * (0.1 * affected + 0.9)) / affected;
  5961.  
  5962.                 Combat::doCombatMana(nullptr, attackerPlayer, tmpDamage, tmpParams);
  5963.             }
  5964.  
  5965.             //Charm rune (attacker as player)
  5966.             if (!damage.extension && target && target->getMonster()) {
  5967.                 MonsterType* mType = g_monsters.getMonsterType(target->getName());
  5968.                 if (mType) {
  5969.                     IOBestiary g_bestiary;
  5970.                     charmRune_t activeCharm = g_bestiary.getCharmFromTarget(attackerPlayer, mType);
  5971.                     if (activeCharm != CHARM_NONE) {
  5972.                         Charm* charm = g_bestiary.getBestiaryCharm(activeCharm);
  5973.                         if (charm && charm->type == CHARM_OFFENSIVE && (charm->chance >= normal_random(0, 100))) {
  5974.                             g_bestiary.parseCharmCombat(charm, attackerPlayer, target, realDamage);
  5975.                         }
  5976.                     }
  5977.                 }
  5978.             }
  5979.         }
  5980.  
  5981.         if (spectators.empty()) {
  5982.             map.getSpectators(spectators, targetPos, true, true);
  5983.         }
  5984.  
  5985.         addCreatureHealth(spectators, target);
  5986.  
  5987.         message.primary.value = damage.primary.value;
  5988.         message.secondary.value = damage.secondary.value;
  5989.  
  5990.         uint8_t hitEffect;
  5991.         if (message.primary.value) {
  5992.             combatGetTypeInfo(damage.primary.type, target, message.primary.color, hitEffect);
  5993.             if (hitEffect != CONST_ME_NONE) {
  5994.                 addMagicEffect(spectators, targetPos, hitEffect);
  5995.             }
  5996.         }
  5997.  
  5998.         if (message.secondary.value) {
  5999.             combatGetTypeInfo(damage.secondary.type, target, message.secondary.color, hitEffect);
  6000.             if (hitEffect != CONST_ME_NONE) {
  6001.                 addMagicEffect(spectators, targetPos, hitEffect);
  6002.             }
  6003.         }
  6004.  
  6005.         if (message.primary.color != TEXTCOLOR_NONE || message.secondary.color != TEXTCOLOR_NONE) {
  6006.             if (attackerPlayer) {
  6007.                 attackerPlayer->updateImpactTracker(damage.primary.type, damage.primary.value);
  6008.                 if (damage.secondary.type != COMBAT_NONE) {
  6009.                     attackerPlayer->updateImpactTracker(damage.secondary.type, damage.secondary.value);
  6010.                 }
  6011.             }
  6012.             if (targetPlayer) {
  6013.                 std::string cause = "(other)";
  6014.                 if (attacker) {
  6015.                     cause = attacker->getName();
  6016.                 }
  6017.  
  6018.                 targetPlayer->updateInputAnalyzer(damage.primary.type, damage.primary.value, cause);
  6019.                 if (attackerPlayer) {
  6020.                     if (damage.secondary.type != COMBAT_NONE) {
  6021.                         attackerPlayer->updateInputAnalyzer(damage.secondary.type, damage.secondary.value, cause);
  6022.                     }
  6023.                 }
  6024.             }
  6025.             std::stringstream ss;
  6026.  
  6027.             ss << realDamage << (realDamage != 1 ? " hitpoints" : " hitpoint");
  6028.             std::string damageString = ss.str();
  6029.  
  6030.             std::string spectatorMessage;
  6031.  
  6032.             for (Creature* spectator : spectators) {
  6033.                 Player* tmpPlayer = spectator->getPlayer();
  6034.                 if (tmpPlayer->getPosition().z != targetPos.z) {
  6035.                     continue;
  6036.                 }
  6037.  
  6038.                 if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
  6039.                     ss.str({});
  6040.                     ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " due to your attack.";
  6041.                     if (damage.extension) {
  6042.                         ss << " " << damage.exString;
  6043.                     }
  6044.                     message.type = MESSAGE_DAMAGE_DEALT;
  6045.                     message.text = ss.str();
  6046.                 } else if (tmpPlayer == targetPlayer) {
  6047.                     ss.str({});
  6048.                     ss << "You lose " << damageString;
  6049.                     if (!attacker) {
  6050.                         ss << '.';
  6051.                     } else if (targetPlayer == attackerPlayer) {
  6052.                         ss << " due to your own attack.";
  6053.                     } else {
  6054.                         ss << " due to an attack by " << attacker->getNameDescription() << '.';
  6055.                     }
  6056.                     if (damage.extension) {
  6057.                         ss << " " << damage.exString;
  6058.                     }
  6059.                     message.type = MESSAGE_DAMAGE_RECEIVED;
  6060.                     message.text = ss.str();
  6061.                 } else {
  6062.                     message.type = MESSAGE_DAMAGE_OTHERS;
  6063.  
  6064.                     if (spectatorMessage.empty()) {
  6065.                         ss.str({});
  6066.                         ss << ucfirst(target->getNameDescription()) << " loses " << damageString;
  6067.                         if (attacker) {
  6068.                             ss << " due to ";
  6069.                             if (attacker == target) {
  6070.                                 if (targetPlayer) {
  6071.                                     ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack");
  6072.                                 } else {
  6073.                                     ss << "its own attack";
  6074.                                 }
  6075.                             } else {
  6076.                                 ss << "an attack by " << attacker->getNameDescription();
  6077.                             }
  6078.                         }
  6079.                         ss << '.';
  6080.                         if (damage.extension) {
  6081.                             ss << " " << damage.exString;
  6082.                         }
  6083.                         spectatorMessage = ss.str();
  6084.                     }
  6085.  
  6086.                     message.text = spectatorMessage;
  6087.                 }
  6088.                 tmpPlayer->sendTextMessage(message);
  6089.             }
  6090.         }
  6091.     }
  6092.  
  6093.     return true;
  6094. }
  6095.  
  6096. bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& damage)
  6097. {
  6098.     const Position& targetPos = target->getPosition();
  6099.     int32_t manaChange = damage.primary.value + damage.secondary.value;
  6100.     if (manaChange > 0) {
  6101.         Player* attackerPlayer;
  6102.         if (attacker) {
  6103.             attackerPlayer = attacker->getPlayer();
  6104.         } else {
  6105.             attackerPlayer = nullptr;
  6106.         }
  6107.  
  6108.         Player* targetPlayer = target->getPlayer();
  6109.         if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
  6110.             return false;
  6111.         }
  6112.  
  6113.         if (damage.origin != ORIGIN_NONE) {
  6114.             const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
  6115.             if (!events.empty()) {
  6116.                 for (CreatureEvent* creatureEvent : events) {
  6117.                     creatureEvent->executeManaChange(target, attacker, damage);
  6118.                 }
  6119.                 damage.origin = ORIGIN_NONE;
  6120.                 return combatChangeMana(attacker, target, damage);
  6121.             }
  6122.         }
  6123.  
  6124.         int32_t realManaChange = target->getMana();
  6125.         target->changeMana(manaChange);
  6126.         realManaChange = target->getMana() - realManaChange;
  6127.  
  6128.         if (realManaChange > 0 && !target->isInGhostMode()) {
  6129.             std::string damageString = std::to_string(realManaChange) + " mana.";
  6130.  
  6131.             std::string spectatorMessage;
  6132.             if (!attacker) {
  6133.                 spectatorMessage += ucfirst(target->getNameDescription());
  6134.                 spectatorMessage += " was restored for " + damageString;
  6135.             } else {
  6136.                 spectatorMessage += ucfirst(attacker->getNameDescription());
  6137.                 spectatorMessage += " restored ";
  6138.                 if (attacker == target) {
  6139.                     spectatorMessage += (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "herself" : "himself") : "itself");
  6140.                 } else {
  6141.                     spectatorMessage += target->getNameDescription();
  6142.                 }
  6143.                 spectatorMessage += " for " + damageString;
  6144.             }
  6145.  
  6146.             TextMessage message;
  6147.             message.position = targetPos;
  6148.             message.primary.value = realManaChange;
  6149.             message.primary.color = TEXTCOLOR_MAYABLUE;
  6150.  
  6151.             SpectatorHashSet spectators;
  6152.             map.getSpectators(spectators, targetPos, false, true);
  6153.             for (Creature* spectator : spectators) {
  6154.                 Player* tmpPlayer = spectator->getPlayer();
  6155.                 if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
  6156.                     message.type = MESSAGE_HEALED;
  6157.                     message.text = "You restored " + target->getNameDescription() + " for " + damageString;
  6158.                 } else if (tmpPlayer == targetPlayer) {
  6159.                     message.type = MESSAGE_HEALED;
  6160.                     if (!attacker) {
  6161.                         message.text = "You were restored for " + damageString;
  6162.                     } else if (targetPlayer == attackerPlayer) {
  6163.                         message.text = "You restore yourself for " + damageString;
  6164.                     } else {
  6165.                         message.text = "You were restored by " + attacker->getNameDescription() + " for " + damageString;
  6166.                     }
  6167.                 } else {
  6168.                     message.type = MESSAGE_HEALED_OTHERS;
  6169.                     message.text = spectatorMessage;
  6170.                 }
  6171.                 tmpPlayer->sendTextMessage(message);
  6172.             }
  6173.         }
  6174.     } else {
  6175.         if (!target->isAttackable()) {
  6176.             if (!target->isInGhostMode()) {
  6177.                 addMagicEffect(targetPos, CONST_ME_POFF);
  6178.             }
  6179.             return false;
  6180.         }
  6181.  
  6182.         Player* attackerPlayer;
  6183.         if (attacker) {
  6184.             attackerPlayer = attacker->getPlayer();
  6185.         } else {
  6186.             attackerPlayer = nullptr;
  6187.         }
  6188.  
  6189.         Player* targetPlayer = target->getPlayer();
  6190.         if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
  6191.             return false;
  6192.         }
  6193.  
  6194.         int32_t manaLoss = std::min<int32_t>(target->getMana(), -manaChange);
  6195.         BlockType_t blockType = target->blockHit(attacker, COMBAT_MANADRAIN, manaLoss);
  6196.         if (blockType != BLOCK_NONE) {
  6197.             addMagicEffect(targetPos, CONST_ME_POFF);
  6198.             return false;
  6199.         }
  6200.  
  6201.         if (manaLoss <= 0) {
  6202.             return true;
  6203.         }
  6204.  
  6205.         if (damage.origin != ORIGIN_NONE) {
  6206.             const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
  6207.             if (!events.empty()) {
  6208.                 for (CreatureEvent* creatureEvent : events) {
  6209.                     creatureEvent->executeManaChange(target, attacker, damage);
  6210.                 }
  6211.                 damage.origin = ORIGIN_NONE;
  6212.                 return combatChangeMana(attacker, target, damage);
  6213.             }
  6214.         }
  6215.  
  6216.         if (targetPlayer && attacker && attacker->getMonster()) {
  6217.             //Charm rune (target as player)
  6218.             MonsterType* mType = g_monsters.getMonsterType(attacker->getName());
  6219.             if (mType) {
  6220.                 IOBestiary g_bestiary;
  6221.                 charmRune_t activeCharm = g_bestiary.getCharmFromTarget(targetPlayer, mType);
  6222.                 if (activeCharm != CHARM_NONE && activeCharm != CHARM_CLEANSE) {
  6223.                     Charm* charm = g_bestiary.getBestiaryCharm(activeCharm);
  6224.                     if (charm && charm->type == CHARM_DEFENSIVE && (charm->chance > normal_random(0, 100))) {
  6225.                         if (g_bestiary.parseCharmCombat(charm, targetPlayer, attacker, manaChange)) {
  6226.                             return false; // Dodge charm
  6227.                         }
  6228.                     }
  6229.                 }
  6230.             }
  6231.         }
  6232.  
  6233.         target->drainMana(attacker, manaLoss);
  6234.  
  6235.         std::stringstream ss;
  6236.  
  6237.         std::string damageString = std::to_string(manaLoss);
  6238.  
  6239.         std::string spectatorMessage;
  6240.  
  6241.         TextMessage message;
  6242.         message.position = targetPos;
  6243.         message.primary.value = manaLoss;
  6244.         message.primary.color = TEXTCOLOR_BLUE;
  6245.  
  6246.         SpectatorHashSet spectators;
  6247.         map.getSpectators(spectators, targetPos, false, true);
  6248.         for (Creature* spectator : spectators) {
  6249.             Player* tmpPlayer = spectator->getPlayer();
  6250.             if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
  6251.                 ss.str({});
  6252.                 ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " mana due to your attack.";
  6253.                 message.type = MESSAGE_DAMAGE_DEALT;
  6254.                 message.text = ss.str();
  6255.             } else if (tmpPlayer == targetPlayer) {
  6256.                 ss.str({});
  6257.                 ss << "You lose " << damageString << " mana";
  6258.                 if (!attacker) {
  6259.                     ss << '.';
  6260.                 } else if (targetPlayer == attackerPlayer) {
  6261.                     ss << " due to your own attack.";
  6262.                 } else {
  6263.                     ss << " mana due to an attack by " << attacker->getNameDescription() << '.';
  6264.                 }
  6265.                 message.type = MESSAGE_DAMAGE_RECEIVED;
  6266.                 message.text = ss.str();
  6267.             } else {
  6268.                 if (spectatorMessage.empty()) {
  6269.                     ss.str({});
  6270.                     ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " mana";
  6271.                     if (attacker) {
  6272.                         ss << " due to ";
  6273.                         if (attacker == target) {
  6274.                             ss << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack") : "its own attack");
  6275.                         } else {
  6276.                             ss << "an attack by " << attacker->getNameDescription();
  6277.                         }
  6278.                     }
  6279.                     ss << '.';
  6280.                     spectatorMessage = ss.str();
  6281.                 }
  6282.                 message.type = MESSAGE_DAMAGE_OTHERS;
  6283.                 message.text = spectatorMessage;
  6284.             }
  6285.             tmpPlayer->sendTextMessage(message);
  6286.         }
  6287.     }
  6288.  
  6289.     return true;
  6290. }
  6291.  
  6292. void Game::addCreatureHealth(const Creature* target)
  6293. {
  6294.     SpectatorHashSet spectators;
  6295.     map.getSpectators(spectators, target->getPosition(), true, true);
  6296.     addCreatureHealth(spectators, target);
  6297. }
  6298.  
  6299. void Game::addCreatureHealth(const SpectatorHashSet& spectators, const Creature* target)
  6300. {
  6301.     uint8_t healthPercent = std::ceil((static_cast<double>(target->getHealth()) / std::max<int32_t>(target->getMaxHealth(), 1)) * 100);
  6302.     if (const Player* targetPlayer = target->getPlayer()) {
  6303.         if (Party* party = targetPlayer->getParty()) {
  6304.             party->updatePlayerHealth(targetPlayer, target, healthPercent);
  6305.         }
  6306.     } else if (const Creature* master = target->getMaster()) {
  6307.         if (const Player* masterPlayer = master->getPlayer()) {
  6308.             if (Party* party = masterPlayer->getParty()) {
  6309.                 party->updatePlayerHealth(masterPlayer, target, healthPercent);
  6310.             }
  6311.         }
  6312.     }
  6313.     for (Creature* spectator : spectators) {
  6314.         if (Player* tmpPlayer = spectator->getPlayer()) {
  6315.             tmpPlayer->sendCreatureHealth(target);
  6316.         }
  6317.     }
  6318. }
  6319.  
  6320. void Game::addPlayerMana(const Player* target)
  6321. {
  6322.     if (Party* party = target->getParty()) {
  6323.         uint8_t manaPercent = std::ceil((static_cast<double>(target->getMana()) / std::max<int32_t>(target->getMaxMana(), 1)) * 100);
  6324.         party->updatePlayerMana(target, manaPercent);
  6325.     }
  6326. }
  6327.  
  6328. void Game::addPlayerVocation(const Player* target)
  6329. {
  6330.     if (Party* party = target->getParty()) {
  6331.         party->updatePlayerVocation(target);
  6332.     }
  6333.  
  6334.     SpectatorHashSet spectators;
  6335.     map.getSpectators(spectators, target->getPosition(), true, true);
  6336.  
  6337.     for (Creature* spectator : spectators) {
  6338.         if (Player* tmpPlayer = spectator->getPlayer()) {
  6339.             tmpPlayer->sendPlayerVocation(target);
  6340.         }
  6341.     }
  6342. }
  6343.  
  6344. void Game::addMagicEffect(const Position& pos, uint8_t effect)
  6345. {
  6346.     SpectatorHashSet spectators;
  6347.     map.getSpectators(spectators, pos, true, true);
  6348.     addMagicEffect(spectators, pos, effect);
  6349. }
  6350.  
  6351. void Game::addMagicEffect(const SpectatorHashSet& spectators, const Position& pos, uint8_t effect)
  6352. {
  6353.     for (Creature* spectator : spectators) {
  6354.         if (Player* tmpPlayer = spectator->getPlayer()) {
  6355.             tmpPlayer->sendMagicEffect(pos, effect);
  6356.         }
  6357.     }
  6358. }
  6359.  
  6360. void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect)
  6361. {
  6362.     SpectatorHashSet spectators;
  6363.     map.getSpectators(spectators, fromPos, false, true);
  6364.     map.getSpectators(spectators, toPos, false, true);
  6365.     addDistanceEffect(spectators, fromPos, toPos, effect);
  6366. }
  6367.  
  6368. void Game::addDistanceEffect(const SpectatorHashSet& spectators, const Position& fromPos, const Position& toPos, uint8_t effect)
  6369. {
  6370.     for (Creature* spectator : spectators) {
  6371.         if (Player* tmpPlayer = spectator->getPlayer()) {
  6372.             tmpPlayer->sendDistanceShoot(fromPos, toPos, effect);
  6373.         }
  6374.     }
  6375. }
  6376.  
  6377. void Game::startDecay(Item* item)
  6378. {
  6379.     if (!item || !item->canDecay()) {
  6380.         return;
  6381.     }
  6382.  
  6383.     ItemDecayState_t decayState = item->getDecaying();
  6384.     if (decayState == DECAYING_TRUE) {
  6385.         return;
  6386.     }
  6387.  
  6388.     if (item->getDuration() > 0) {
  6389.         item->incrementReferenceCounter();
  6390.         item->setDecaying(DECAYING_TRUE);
  6391.         toDecayItems.push_front(item);
  6392.     } else {
  6393.         internalDecayItem(item);
  6394.     }
  6395. }
  6396.  
  6397. void Game::internalDecayItem(Item* item)
  6398. {
  6399.     const ItemType& it = Item::items[item->getID()];
  6400.     if (it.decayTo != 0) {
  6401.         Player* player = item->getHoldingPlayer();
  6402.         if (player) {
  6403.             bool needUpdateSkills = false;
  6404.             for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) {
  6405.                 if (it.abilities && it.abilities->skills[i] != 0) {
  6406.                     needUpdateSkills = true;
  6407.                     player->setVarSkill(static_cast<skills_t>(i), -it.abilities->skills[i]);
  6408.                 }
  6409.             }
  6410.  
  6411.             if (needUpdateSkills) {
  6412.                 player->sendSkills();
  6413.             }
  6414.  
  6415.             bool needUpdateStats = false;
  6416.             for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) {
  6417.                 if (it.abilities && it.abilities->stats[s] != 0) {
  6418.                     needUpdateStats = true;
  6419.                     needUpdateSkills = true;
  6420.                     player->setVarStats(static_cast<stats_t>(s), -it.abilities->stats[s]);
  6421.                 }
  6422.                 if (it.abilities && it.abilities->statsPercent[s] != 0) {
  6423.                     needUpdateStats = true;
  6424.                     player->setVarStats(static_cast<stats_t>(s), -static_cast<int32_t>(player->getDefaultStats(static_cast<stats_t>(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f)));
  6425.                 }
  6426.             }
  6427.  
  6428.             if (needUpdateStats) {
  6429.                 player->sendStats();
  6430.             }
  6431.  
  6432.             if (needUpdateSkills) {
  6433.                 player->sendSkills();
  6434.             }
  6435.         }
  6436.         Item* newItem = transformItem(item, it.decayTo);
  6437.         startDecay(newItem);
  6438.     } else {
  6439.         ReturnValue ret = internalRemoveItem(item);
  6440.         if (ret != RETURNVALUE_NOERROR) {
  6441.             SPDLOG_DEBUG("Game::internalDecayItem] internalDecayItem failed, "
  6442.                          "error code: {}, item id: {}",
  6443.                          static_cast<uint32_t>(ret), item->getID());
  6444.         }
  6445.     }
  6446. }
  6447.  
  6448. void Game::checkDecay()
  6449. {
  6450.     g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this)));
  6451.  
  6452.     size_t bucket = (lastBucket + 1) % EVENT_DECAY_BUCKETS;
  6453.  
  6454.     auto it = decayItems[bucket].begin(), end = decayItems[bucket].end();
  6455.     while (it != end) {
  6456.         Item* item = *it;
  6457.         if (!item->canDecay()) {
  6458.             item->setDecaying(DECAYING_FALSE);
  6459.             ReleaseItem(item);
  6460.             it = decayItems[bucket].erase(it);
  6461.             continue;
  6462.         }
  6463.  
  6464.         int32_t duration = item->getDuration();
  6465.         int32_t decreaseTime = std::min<int32_t>(EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS, duration);
  6466.  
  6467.         duration -= decreaseTime;
  6468.         item->decreaseDuration(decreaseTime);
  6469.  
  6470.         if (duration <= 0) {
  6471.             it = decayItems[bucket].erase(it);
  6472.             internalDecayItem(item);
  6473.             ReleaseItem(item);
  6474.         } else if (duration < EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) {
  6475.             it = decayItems[bucket].erase(it);
  6476.             size_t newBucket = (bucket + ((duration + EVENT_DECAYINTERVAL / 2) / 1000)) % EVENT_DECAY_BUCKETS;
  6477.             if (newBucket == bucket) {
  6478.                 internalDecayItem(item);
  6479.                 ReleaseItem(item);
  6480.             } else {
  6481.                 decayItems[newBucket].push_back(item);
  6482.             }
  6483.         } else {
  6484.             ++it;
  6485.         }
  6486.     }
  6487.  
  6488.     lastBucket = bucket;
  6489.     cleanup();
  6490. }
  6491.  
  6492. void Game::checkImbuements()
  6493. {
  6494.     g_scheduler.addEvent(createSchedulerTask(EVENT_IMBUEMENTINTERVAL, std::bind(&Game::checkImbuements, this)));
  6495.  
  6496.     size_t bucket = (lastImbuedBucket + 1) % EVENT_IMBUEMENT_BUCKETS;
  6497.  
  6498.     auto it = imbuedItems[bucket].begin(), end = imbuedItems[bucket].end();
  6499.     while (it != end) {
  6500.         Item* item = *it;
  6501.  
  6502.         if (item->isRemoved() || !item->getParent()->getCreature()) {
  6503.             ReleaseItem(item);
  6504.             it = imbuedItems[bucket].erase(it);
  6505.             continue;
  6506.         }
  6507.  
  6508.         Player* player = item->getParent()->getCreature()->getPlayer();
  6509.         const ItemType& itemType = Item::items[item->getID()];
  6510.         if (!player->hasCondition(CONDITION_INFIGHT) && !itemType.isContainer()) {
  6511.             it++;
  6512.             continue;
  6513.         }
  6514.  
  6515.         bool needUpdate = false;
  6516.         uint8_t slots = Item::items[item->getID()].imbuingSlots;
  6517.         for (uint8_t slot = 0; slot < slots; slot++) {
  6518.             uint32_t info = item->getImbuement(slot);
  6519.             int32_t duration = info >> 8;
  6520.             int32_t newDuration = std::max(0, (duration - (EVENT_IMBUEMENTINTERVAL * EVENT_IMBUEMENT_BUCKETS) / 690));
  6521.             if (duration > 0 && newDuration == 0) {
  6522.                 needUpdate = true;
  6523.             }
  6524.         }
  6525.  
  6526.         int32_t index = player ? player->getThingIndex(item) : -1;
  6527.         needUpdate = needUpdate && index != -1;
  6528.  
  6529.         if (needUpdate) {
  6530.             player->postRemoveNotification(item, player, index);
  6531.             ReleaseItem(item);
  6532.             it = imbuedItems[bucket].erase(it);
  6533.             for (uint8_t slot = 0; slot < slots; slot++) {
  6534.                 item->setImbuement(slot, 0);
  6535.             }
  6536.         }
  6537.  
  6538.         for (uint8_t slot = 0; slot < slots; slot++) {
  6539.             uint32_t info = item->getImbuement(slot);
  6540.             int32_t duration = info >> 8;
  6541.             int32_t decreaseTime = std::min<int32_t>((EVENT_IMBUEMENTINTERVAL * EVENT_IMBUEMENT_BUCKETS) / 690, duration);
  6542.             duration -= decreaseTime;
  6543.  
  6544.             int32_t id = info & 0xFF;
  6545.             int64_t newinfo = (duration << 8) | id;
  6546.             item->setImbuement(slot, newinfo);
  6547.         }
  6548.  
  6549.         if (needUpdate) {
  6550.             player->postAddNotification(item, player, index);
  6551.         } else {
  6552.             it++;
  6553.         }
  6554.     }
  6555.  
  6556.     lastImbuedBucket = bucket;
  6557.     cleanup();
  6558. }
  6559.  
  6560. void Game::checkLight()
  6561. {
  6562.     g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL_MS, std::bind(&Game::checkLight, this)));
  6563.  
  6564.     lightHour += lightHourDelta;
  6565.  
  6566.     if (lightHour > LIGHT_DAY_LENGTH) {
  6567.         lightHour -= LIGHT_DAY_LENGTH;
  6568.     }
  6569.  
  6570.     if (std::abs(lightHour - SUNRISE) < 2 * lightHourDelta) {
  6571.         lightState = LIGHT_STATE_SUNRISE;
  6572.     } else if (std::abs(lightHour - SUNSET) < 2 * lightHourDelta) {
  6573.         lightState = LIGHT_STATE_SUNSET;
  6574.     }
  6575.  
  6576.     int32_t newLightLevel = lightLevel;
  6577.     bool lightChange = false;
  6578.  
  6579.     switch (lightState) {
  6580.         case LIGHT_STATE_SUNRISE: {
  6581.             newLightLevel += (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30;
  6582.             lightChange = true;
  6583.             break;
  6584.         }
  6585.         case LIGHT_STATE_SUNSET: {
  6586.             newLightLevel -= (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30;
  6587.             lightChange = true;
  6588.             break;
  6589.         }
  6590.         default:
  6591.             break;
  6592.     }
  6593.  
  6594.     if (newLightLevel <= LIGHT_LEVEL_NIGHT) {
  6595.         lightLevel = LIGHT_LEVEL_NIGHT;
  6596.         lightState = LIGHT_STATE_NIGHT;
  6597.     } else if (newLightLevel >= LIGHT_LEVEL_DAY) {
  6598.         lightLevel = LIGHT_LEVEL_DAY;
  6599.         lightState = LIGHT_STATE_DAY;
  6600.     } else {
  6601.         lightLevel = newLightLevel;
  6602.     }
  6603.  
  6604.     LightInfo lightInfo = getWorldLightInfo();
  6605.  
  6606.     if (lightChange) {
  6607.         for (const auto& it : players) {
  6608.             it.second->sendWorldLight(lightInfo);
  6609.       it.second->sendTibiaTime(lightHour);
  6610.         }
  6611.     } else {
  6612.         for (const auto& it : players) {
  6613.             it.second->sendTibiaTime(lightHour);
  6614.     }
  6615.     }
  6616.   if (currentLightState != lightState) {
  6617.         currentLightState = lightState;
  6618.         for (auto& it : g_globalEvents->getEventMap(GLOBALEVENT_PERIODCHANGE)) {
  6619.             it.second.executePeriodChange(lightState, lightInfo);
  6620.         }
  6621.     }
  6622. }
  6623.  
  6624. LightInfo Game::getWorldLightInfo() const
  6625. {
  6626.     return {lightLevel, 0xD7};
  6627. }
  6628.  
  6629. bool Game::gameIsDay()
  6630. {
  6631.     if (lightHour >= (6 * 60) && lightHour <= (18 * 60)) {
  6632.         isDay = true;
  6633.     } else {
  6634.         isDay = false;
  6635.     }
  6636.  
  6637.     return isDay;
  6638. }
  6639.  
  6640. void Game::shutdown()
  6641. {
  6642.   webhook_send_message("Server is shutting down", "Shutting down...", WEBHOOK_COLOR_OFFLINE);
  6643.  
  6644.     SPDLOG_INFO("Shutting down...");
  6645.  
  6646.     g_scheduler.shutdown();
  6647.     g_databaseTasks.shutdown();
  6648.     g_dispatcher.shutdown();
  6649.     map.spawns.clear();
  6650.     raids.clear();
  6651.  
  6652.     cleanup();
  6653.  
  6654.     if (serviceManager) {
  6655.         serviceManager->stop();
  6656.     }
  6657.  
  6658.     ConnectionManager::getInstance().closeAll();
  6659.  
  6660.     SPDLOG_INFO("Done!");
  6661. }
  6662.  
  6663. void Game::cleanup()
  6664. {
  6665.     //free memory
  6666.     for (auto creature : ToReleaseCreatures) {
  6667.         creature->decrementReferenceCounter();
  6668.     }
  6669.     ToReleaseCreatures.clear();
  6670.  
  6671.     for (auto item : ToReleaseItems) {
  6672.         item->decrementReferenceCounter();
  6673.     }
  6674.     ToReleaseItems.clear();
  6675.  
  6676.     for (Item* item : toDecayItems) {
  6677.         const uint32_t dur = item->getDuration();
  6678.         if (dur >= EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) {
  6679.             decayItems[lastBucket].push_back(item);
  6680.         } else {
  6681.             decayItems[(lastBucket + 1 + dur / 1000) % EVENT_DECAY_BUCKETS].push_back(item);
  6682.         }
  6683.     }
  6684.     toDecayItems.clear();
  6685.  
  6686.     for (Item* item : toImbuedItems) {
  6687.         imbuedItems[lastImbuedBucket].push_back(item);
  6688.     }
  6689.     toImbuedItems.clear();
  6690.  
  6691. }
  6692.  
  6693. void Game::ReleaseCreature(Creature* creature)
  6694. {
  6695.     ToReleaseCreatures.push_back(creature);
  6696. }
  6697.  
  6698. void Game::ReleaseItem(Item* item)
  6699. {
  6700.     ToReleaseItems.push_back(item);
  6701. }
  6702.  
  6703. void Game::addBestiaryList(uint16_t raceid, std::string name)
  6704. {
  6705.     auto it = BestiaryList.find(raceid);
  6706.     if (it != BestiaryList.end()) {
  6707.         return;
  6708.     }
  6709.     BestiaryList.insert(std::pair<uint16_t, std::string>(raceid, name));
  6710. }
  6711.  
  6712. void Game::broadcastMessage(const std::string& text, MessageClasses type) const
  6713. {
  6714.     SPDLOG_INFO("Broadcasted message: {}", text);
  6715.     for (const auto& it : players) {
  6716.         it.second->sendTextMessage(type, text);
  6717.     }
  6718. }
  6719.  
  6720. void Game::updateCreatureWalkthrough(const Creature* creature)
  6721. {
  6722.     //send to clients
  6723.     SpectatorHashSet spectators;
  6724.     map.getSpectators(spectators, creature->getPosition(), true, true);
  6725.     for (Creature* spectator : spectators) {
  6726.         Player* tmpPlayer = spectator->getPlayer();
  6727.         tmpPlayer->sendCreatureWalkthrough(creature, tmpPlayer->canWalkthroughEx(creature));
  6728.     }
  6729. }
  6730.  
  6731. void Game::updateCreatureSkull(const Creature* creature)
  6732. {
  6733.     if (getWorldType() != WORLD_TYPE_PVP) {
  6734.         return;
  6735.     }
  6736.  
  6737.     SpectatorHashSet spectators;
  6738.     map.getSpectators(spectators, creature->getPosition(), true, true);
  6739.     for (Creature* spectator : spectators) {
  6740.         spectator->getPlayer()->sendCreatureSkull(creature);
  6741.     }
  6742. }
  6743.  
  6744. void Game::updatePlayerShield(Player* player)
  6745. {
  6746.     SpectatorHashSet spectators;
  6747.     map.getSpectators(spectators, player->getPosition(), true, true);
  6748.     for (Creature* spectator : spectators) {
  6749.         spectator->getPlayer()->sendCreatureShield(player);
  6750.     }
  6751. }
  6752.  
  6753. void Game::updateCreatureType(Creature* creature)
  6754. {
  6755.     const Player* masterPlayer = nullptr;
  6756.  
  6757.     CreatureType_t creatureType = creature->getType();
  6758.     if (creatureType == CREATURETYPE_MONSTER) {
  6759.         const Creature* master = creature->getMaster();
  6760.         if (master) {
  6761.             masterPlayer = master->getPlayer();
  6762.             if (masterPlayer) {
  6763.                 creatureType = CREATURETYPE_SUMMONPLAYER;
  6764.             }
  6765.         }
  6766.     }
  6767.  
  6768.     //send to clients
  6769.     SpectatorHashSet spectators;
  6770.     map.getSpectators(spectators, creature->getPosition(), true, true);
  6771.  
  6772.     for (Creature* spectator : spectators) {
  6773.         spectator->getPlayer()->sendCreatureType(creature, creatureType);
  6774.     }
  6775. }
  6776.  
  6777. void Game::updatePremium(account::Account& account)
  6778. {
  6779. bool save = false;
  6780.     time_t timeNow = time(nullptr);
  6781.     uint32_t rem_days = 0;
  6782.     time_t last_day;
  6783.     account.GetPremiumRemaningDays(&rem_days);
  6784.     account.GetPremiumLastDay(&last_day);
  6785.     std::string name;
  6786.     if (rem_days != 0) {
  6787.         if (last_day == 0) {
  6788.             account.SetPremiumLastDay(timeNow);
  6789.             save = true;
  6790.         } else {
  6791.             uint32_t days = (timeNow - last_day) / 86400;
  6792.             if (days > 0) {
  6793.                 if (days >= rem_days) {
  6794.                     if(!account.SetPremiumRemaningDays(0) || !account.SetPremiumLastDay(0)) {
  6795.                         account.GetName(&name);
  6796.                         SPDLOG_ERROR("Failed to set account premium days, account name: {}",
  6797.                             name);
  6798.                     }
  6799.                 } else {
  6800.                     account.SetPremiumRemaningDays((rem_days - days));
  6801.                     time_t remainder = (timeNow - last_day) % 86400;
  6802.                     account.SetPremiumLastDay(timeNow - remainder);
  6803.                 }
  6804.  
  6805.                 save = true;
  6806.             }
  6807.         }
  6808.   }
  6809.   else if (last_day != 0)
  6810.   {
  6811.     account.SetPremiumLastDay(0);
  6812.         save = true;
  6813.   }
  6814.  
  6815.     if (save && !account.SaveAccountDB()) {
  6816.         account.GetName(&name);
  6817.         SPDLOG_ERROR("Failed to save account: {}",
  6818.             name);
  6819.     }
  6820. }
  6821.  
  6822. void Game::loadMotdNum()
  6823. {
  6824.     Database& db = Database::getInstance();
  6825.  
  6826.     DBResult_ptr result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'");
  6827.     if (result) {
  6828.         motdNum = result->getNumber<uint32_t>("value");
  6829.     } else {
  6830.         db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')");
  6831.     }
  6832.  
  6833.     result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'");
  6834.     if (result) {
  6835.         motdHash = result->getString("value");
  6836.         if (motdHash != transformToSHA1(g_config.getString(ConfigManager::MOTD))) {
  6837.             ++motdNum;
  6838.         }
  6839.     } else {
  6840.         db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')");
  6841.     }
  6842. }
  6843.  
  6844. void Game::saveMotdNum() const
  6845. {
  6846.     Database& db = Database::getInstance();
  6847.  
  6848.     std::ostringstream query;
  6849.     query << "UPDATE `server_config` SET `value` = '" << motdNum << "' WHERE `config` = 'motd_num'";
  6850.     db.executeQuery(query.str());
  6851.  
  6852.     query.str(std::string());
  6853.     query << "UPDATE `server_config` SET `value` = '" << transformToSHA1(g_config.getString(ConfigManager::MOTD)) << "' WHERE `config` = 'motd_hash'";
  6854.     db.executeQuery(query.str());
  6855. }
  6856.  
  6857. void Game::checkPlayersRecord()
  6858. {
  6859.     const size_t playersOnline = getPlayersOnline();
  6860.     if (playersOnline > playersRecord) {
  6861.         uint32_t previousRecord = playersRecord;
  6862.         playersRecord = playersOnline;
  6863.  
  6864.         for (auto& it : g_globalEvents->getEventMap(GLOBALEVENT_RECORD)) {
  6865.             it.second.executeRecord(playersRecord, previousRecord);
  6866.         }
  6867.         updatePlayersRecord();
  6868.     }
  6869. }
  6870.  
  6871. void Game::updatePlayersRecord() const
  6872. {
  6873.     Database& db = Database::getInstance();
  6874.  
  6875.     std::ostringstream query;
  6876.     query << "UPDATE `server_config` SET `value` = '" << playersRecord << "' WHERE `config` = 'players_record'";
  6877.     db.executeQuery(query.str());
  6878. }
  6879.  
  6880. void Game::loadPlayersRecord()
  6881. {
  6882.     Database& db = Database::getInstance();
  6883.  
  6884.     DBResult_ptr result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'players_record'");
  6885.     if (result) {
  6886.         playersRecord = result->getNumber<uint32_t>("value");
  6887.     } else {
  6888.         db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('players_record', '0')");
  6889.     }
  6890. }
  6891.  
  6892. void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId)
  6893. {
  6894.     //Prevent crafted packets from inviting urself to a party (using OTClient)
  6895.     if (playerId == invitedId) {
  6896.         return;
  6897.     }
  6898.  
  6899.     Player* player = getPlayerByID(playerId);
  6900.     if (!player) {
  6901.         return;
  6902.     }
  6903.  
  6904.     Player* invitedPlayer = getPlayerByID(invitedId);
  6905.     if (!invitedPlayer || invitedPlayer->isInviting(player)) {
  6906.         return;
  6907.     }
  6908.  
  6909.     if (invitedPlayer->getParty()) {
  6910.         std::ostringstream ss;
  6911.         ss << invitedPlayer->getName() << " is already in a party.";
  6912.         player->sendTextMessage(MESSAGE_PARTY_MANAGEMENT, ss.str());
  6913.         return;
  6914.     }
  6915.  
  6916.     Party* party = player->getParty();
  6917.     if (!party) {
  6918.         party = new Party(player);
  6919.     } else if (party->getLeader() != player) {
  6920.         return;
  6921.     }
  6922.  
  6923.     party->invitePlayer(*invitedPlayer);
  6924. }
  6925.  
  6926. void Game::playerJoinParty(uint32_t playerId, uint32_t leaderId)
  6927. {
  6928.     Player* player = getPlayerByID(playerId);
  6929.     if (!player) {
  6930.         return;
  6931.     }
  6932.  
  6933.     Player* leader = getPlayerByID(leaderId);
  6934.     if (!leader || !leader->isInviting(player)) {
  6935.         return;
  6936.     }
  6937.  
  6938.     Party* party = leader->getParty();
  6939.     if (!party || party->getLeader() != leader) {
  6940.         return;
  6941.     }
  6942.  
  6943.     if (player->getParty()) {
  6944.         player->sendTextMessage(MESSAGE_PARTY_MANAGEMENT, "You are already in a party.");
  6945.         return;
  6946.     }
  6947.  
  6948.     party->joinParty(*player);
  6949. }
  6950.  
  6951. void Game::playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId)
  6952. {
  6953.     Player* player = getPlayerByID(playerId);
  6954.     if (!player) {
  6955.         return;
  6956.     }
  6957.  
  6958.     Party* party = player->getParty();
  6959.     if (!party || party->getLeader() != player) {
  6960.         return;
  6961.     }
  6962.  
  6963.     Player* invitedPlayer = getPlayerByID(invitedId);
  6964.     if (!invitedPlayer || !player->isInviting(invitedPlayer)) {
  6965.         return;
  6966.     }
  6967.  
  6968.     party->revokeInvitation(*invitedPlayer);
  6969. }
  6970.  
  6971. void Game::playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId)
  6972. {
  6973.     Player* player = getPlayerByID(playerId);
  6974.     if (!player) {
  6975.         return;
  6976.     }
  6977.  
  6978.     Party* party = player->getParty();
  6979.     if (!party || party->getLeader() != player) {
  6980.         return;
  6981.     }
  6982.  
  6983.     Player* newLeader = getPlayerByID(newLeaderId);
  6984.     if (!newLeader || !player->isPartner(newLeader)) {
  6985.         return;
  6986.     }
  6987.  
  6988.     party->passPartyLeadership(newLeader);
  6989. }
  6990.  
  6991. void Game::playerLeaveParty(uint32_t playerId)
  6992. {
  6993.     Player* player = getPlayerByID(playerId);
  6994.     if (!player) {
  6995.         return;
  6996.     }
  6997.  
  6998.     Party* party = player->getParty();
  6999.     if (!party || player->hasCondition(CONDITION_INFIGHT)) {
  7000.         return;
  7001.     }
  7002.  
  7003.     party->leaveParty(player);
  7004. }
  7005.  
  7006. void Game::playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive)
  7007. {
  7008.     Player* player = getPlayerByID(playerId);
  7009.     if (!player) {
  7010.         return;
  7011.     }
  7012.  
  7013.     Party* party = player->getParty();
  7014.     Tile* playerTile = player->getTile();
  7015.     if (!party || (player->hasCondition(CONDITION_INFIGHT) && playerTile && !playerTile->hasFlag(TILESTATE_PROTECTIONZONE))) {
  7016.         return;
  7017.     }
  7018.  
  7019.     party->setSharedExperience(player, sharedExpActive);
  7020. }
  7021.  
  7022. void Game::sendGuildMotd(uint32_t playerId)
  7023. {
  7024.     Player* player = getPlayerByID(playerId);
  7025.     if (!player) {
  7026.         return;
  7027.     }
  7028.  
  7029.     Guild* guild = player->getGuild();
  7030.     if (guild) {
  7031.         player->sendChannelMessage("Message of the Day", guild->getMotd(), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD);
  7032.     }
  7033. }
  7034.  
  7035. void Game::kickPlayer(uint32_t playerId, bool displayEffect)
  7036. {
  7037.     Player* player = getPlayerByID(playerId);
  7038.     if (!player) {
  7039.         return;
  7040.     }
  7041.  
  7042.     player->kickPlayer(displayEffect);
  7043. }
  7044.  
  7045. void Game::playerCyclopediaCharacterInfo(Player* player, uint32_t characterID, CyclopediaCharacterInfoType_t characterInfoType, uint16_t entriesPerPage, uint16_t page) {
  7046.     uint32_t playerGUID = player->getGUID();
  7047.   if (characterID != playerGUID) {
  7048.         //For now allow viewing only our character since we don't have tournaments supported
  7049.         player->sendCyclopediaCharacterNoData(characterInfoType, 2);
  7050.         return;
  7051.     }
  7052.  
  7053.     switch (characterInfoType) {
  7054.     case CYCLOPEDIA_CHARACTERINFO_BASEINFORMATION: player->sendCyclopediaCharacterBaseInformation(); break;
  7055.     case CYCLOPEDIA_CHARACTERINFO_GENERALSTATS: player->sendCyclopediaCharacterGeneralStats(); break;
  7056.     case CYCLOPEDIA_CHARACTERINFO_COMBATSTATS: player->sendCyclopediaCharacterCombatStats(); break;
  7057.   case CYCLOPEDIA_CHARACTERINFO_RECENTDEATHS: {
  7058.     std::ostringstream query;
  7059.     uint32_t offset = static_cast<uint32_t>(page - 1) * entriesPerPage;
  7060.             query << "SELECT `time`, `level`, `killed_by`, `mostdamage_by`, (select count(*) FROM `player_deaths` WHERE `player_id` = " << playerGUID << ") as `entries` FROM `player_deaths` WHERE `player_id` = " << playerGUID << " ORDER BY `time` DESC LIMIT " << offset << ", " << entriesPerPage;
  7061.  
  7062.             uint32_t playerID = player->getID();
  7063.             std::function<void(DBResult_ptr, bool)> callback = [playerID, page, entriesPerPage](DBResult_ptr result, bool) {
  7064.                 Player* player = g_game.getPlayerByID(playerID);
  7065.                 if (!player) {
  7066.                     return;
  7067.                 }
  7068.  
  7069.                 player->resetAsyncOngoingTask(PlayerAsyncTask_RecentDeaths);
  7070.                 if (!result) {
  7071.                     player->sendCyclopediaCharacterRecentDeaths(0, 0, {});
  7072.                     return;
  7073.                 }
  7074.  
  7075.                 uint32_t pages = result->getNumber<uint32_t>("entries");
  7076.                 pages += entriesPerPage - 1;
  7077.                 pages /= entriesPerPage;
  7078.  
  7079.                 std::vector<RecentDeathEntry> entries;
  7080.                 entries.reserve(result->countResults());
  7081.                 do {
  7082.                     std::string cause1 = result->getString("killed_by");
  7083.                     std::string cause2 = result->getString("mostdamage_by");
  7084.  
  7085.                     std::ostringstream cause;
  7086.                     cause << "Died at Level " << result->getNumber<uint32_t>("level") << " by";
  7087.                     if (!cause1.empty()) {
  7088.                         const char& character = cause1.front();
  7089.                         if (character == 'a' || character == 'e' || character == 'i' || character == 'o' || character == 'u') {
  7090.                             cause << " an ";
  7091.                         } else {
  7092.                             cause << " a ";
  7093.                         }
  7094.                         cause << cause1;
  7095.                     }
  7096.  
  7097.                     if (!cause2.empty()) {
  7098.                         if (!cause1.empty()) {
  7099.                             cause << " and ";
  7100.                         }
  7101.  
  7102.                         const char& character = cause2.front();
  7103.                         if (character == 'a' || character == 'e' || character == 'i' || character == 'o' || character == 'u') {
  7104.                             cause << " an ";
  7105.                         } else {
  7106.                             cause << " a ";
  7107.                         }
  7108.                         cause << cause2;
  7109.                     }
  7110.                     cause << '.';
  7111.                     entries.emplace_back(std::move(cause.str()), result->getNumber<uint32_t>("time"));
  7112.                 } while (result->next());
  7113.                 player->sendCyclopediaCharacterRecentDeaths(page, static_cast<uint16_t>(pages), entries);
  7114.             };
  7115.             g_databaseTasks.addTask(std::move(query.str()), callback, true);
  7116.             player->addAsyncOngoingTask(PlayerAsyncTask_RecentDeaths);
  7117.             break;
  7118.     }
  7119.     case CYCLOPEDIA_CHARACTERINFO_RECENTPVPKILLS: {
  7120.             // TODO: add guildwar, assists and arena kills
  7121.             Database& db = Database::getInstance();
  7122.             const std::string& escapedName = db.escapeString(player->getName());
  7123.             std::ostringstream query;
  7124.             uint32_t offset = static_cast<uint32_t>(page - 1) * entriesPerPage;
  7125.             query << "SELECT `d`.`time`, `d`.`killed_by`, `d`.`mostdamage_by`, `d`.`unjustified`, `d`.`mostdamage_unjustified`, `p`.`name`, (select count(*) FROM `player_deaths` WHERE ((`killed_by` = " << escapedName << " AND `is_player` = 1) OR (`mostdamage_by` = " << escapedName << " AND `mostdamage_is_player` = 1))) as `entries` FROM `player_deaths` AS `d` INNER JOIN `players` AS `p` ON `d`.`player_id` = `p`.`id` WHERE ((`d`.`killed_by` = " << escapedName << " AND `d`.`is_player` = 1) OR (`d`.`mostdamage_by` = " << escapedName << " AND `d`.`mostdamage_is_player` = 1)) ORDER BY `time` DESC LIMIT " << offset << ", " << entriesPerPage;
  7126.  
  7127.             uint32_t playerID = player->getID();
  7128.             std::function<void(DBResult_ptr, bool)> callback = [playerID, page, entriesPerPage](DBResult_ptr result, bool) {
  7129.                 Player* player = g_game.getPlayerByID(playerID);
  7130.                 if (!player) {
  7131.                     return;
  7132.                 }
  7133.  
  7134.                 player->resetAsyncOngoingTask(PlayerAsyncTask_RecentPvPKills);
  7135.                 if (!result) {
  7136.                     player->sendCyclopediaCharacterRecentPvPKills(0, 0, {});
  7137.                     return;
  7138.                 }
  7139.  
  7140.                 uint32_t pages = result->getNumber<uint32_t>("entries");
  7141.                 pages += entriesPerPage - 1;
  7142.                 pages /= entriesPerPage;
  7143.  
  7144.                 std::vector<RecentPvPKillEntry> entries;
  7145.                 entries.reserve(result->countResults());
  7146.                 do {
  7147.                     std::string cause1 = result->getString("killed_by");
  7148.                     std::string cause2 = result->getString("mostdamage_by");
  7149.                     std::string name = result->getString("name");
  7150.  
  7151.                     uint8_t status = CYCLOPEDIA_CHARACTERINFO_RECENTKILLSTATUS_JUSTIFIED;
  7152.                     if (player->getName() == cause1) {
  7153.                         if (result->getNumber<uint32_t>("unjustified") == 1) {
  7154.                             status = CYCLOPEDIA_CHARACTERINFO_RECENTKILLSTATUS_UNJUSTIFIED;
  7155.                         }
  7156.                     } else if (player->getName() == cause2) {
  7157.                         if (result->getNumber<uint32_t>("mostdamage_unjustified") == 1) {
  7158.                             status = CYCLOPEDIA_CHARACTERINFO_RECENTKILLSTATUS_UNJUSTIFIED;
  7159.                         }
  7160.                     }
  7161.  
  7162.                     std::ostringstream description;
  7163.                     description << "Killed " << name << '.';
  7164.                     entries.emplace_back(std::move(description.str()), result->getNumber<uint32_t>("time"), status);
  7165.                 } while (result->next());
  7166.                 player->sendCyclopediaCharacterRecentPvPKills(page, static_cast<uint16_t>(pages), entries);
  7167.             };
  7168.             g_databaseTasks.addTask(std::move(query.str()), callback, true);
  7169.             player->addAsyncOngoingTask(PlayerAsyncTask_RecentPvPKills);
  7170.             break;
  7171.     }
  7172.     case CYCLOPEDIA_CHARACTERINFO_ACHIEVEMENTS: player->sendCyclopediaCharacterAchievements(); break;
  7173.     case CYCLOPEDIA_CHARACTERINFO_ITEMSUMMARY: player->sendCyclopediaCharacterItemSummary(); break;
  7174.     case CYCLOPEDIA_CHARACTERINFO_OUTFITSMOUNTS: player->sendCyclopediaCharacterOutfitsMounts(); break;
  7175.     case CYCLOPEDIA_CHARACTERINFO_STORESUMMARY: player->sendCyclopediaCharacterStoreSummary(); break;
  7176.     case CYCLOPEDIA_CHARACTERINFO_INSPECTION: player->sendCyclopediaCharacterInspection(); break;
  7177.     case CYCLOPEDIA_CHARACTERINFO_BADGES: player->sendCyclopediaCharacterBadges(); break;
  7178.     case CYCLOPEDIA_CHARACTERINFO_TITLES: player->sendCyclopediaCharacterTitles(); break;
  7179.   default: player->sendCyclopediaCharacterNoData(characterInfoType, 1); break;
  7180.     }
  7181. }
  7182.  
  7183. void Game::playerHighscores(Player* player, HighscoreType_t type, uint8_t category, uint32_t vocation, const std::string&, uint16_t page, uint8_t entriesPerPage)
  7184. {
  7185.     if (player->hasAsyncOngoingTask(PlayerAsyncTask_Highscore)) {
  7186.         return;
  7187.     }
  7188.  
  7189.     std::string categoryName;
  7190.     switch (category) {
  7191.         case HIGHSCORE_CATEGORY_FIST_FIGHTING: categoryName = "skill_fist"; break;
  7192.         case HIGHSCORE_CATEGORY_CLUB_FIGHTING: categoryName = "skill_club"; break;
  7193.         case HIGHSCORE_CATEGORY_SWORD_FIGHTING: categoryName = "skill_sword"; break;
  7194.         case HIGHSCORE_CATEGORY_AXE_FIGHTING: categoryName = "skill_axe"; break;
  7195.         case HIGHSCORE_CATEGORY_DISTANCE_FIGHTING: categoryName = "skill_dist"; break;
  7196.         case HIGHSCORE_CATEGORY_SHIELDING: categoryName = "skill_shielding"; break;
  7197.         case HIGHSCORE_CATEGORY_FISHING: categoryName = "skill_fishing"; break;
  7198.         case HIGHSCORE_CATEGORY_MAGIC_LEVEL: categoryName = "maglevel"; break;
  7199.         default: {
  7200.             category = HIGHSCORE_CATEGORY_EXPERIENCE;
  7201.             categoryName = "experience";
  7202.             break;
  7203.         }
  7204.     }
  7205.  
  7206.     std::ostringstream query;
  7207.     if (type == HIGHSCORE_GETENTRIES) {
  7208.         uint32_t startPage = (static_cast<uint32_t>(page - 1) * static_cast<uint32_t>(entriesPerPage));
  7209.         uint32_t endPage = startPage + static_cast<uint32_t>(entriesPerPage);
  7210.         query << "SELECT *, @row AS `entries`, " << page << " AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn` FROM (SELECT `id`, `name`, `level`, `vocation`, `" << categoryName << "` AS `points`, @curRank := IF(@prevRank = `" << categoryName << "`, @curRank, IF(@prevRank := `" << categoryName << "`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0) `r` WHERE `group_id` < " << static_cast<int>(account::GroupType::GROUP_TYPE_GAMEMASTER) << " ORDER BY `" << categoryName << "` DESC) `t`";
  7211.         if (vocation != 0xFFFFFFFF) {
  7212.             bool firstVocation = true;
  7213.  
  7214.             const auto& vocationsMap = g_vocations.getVocations();
  7215.             for (const auto& it : vocationsMap) {
  7216.                 const Vocation& voc = it.second;
  7217.                 if (voc.getFromVocation() == vocation) {
  7218.                     if (firstVocation) {
  7219.                         query << " WHERE `vocation` = " << voc.getId();
  7220.                         firstVocation = false;
  7221.                     } else {
  7222.                         query << " OR `vocation` = " << voc.getId();
  7223.                     }
  7224.                 }
  7225.             }
  7226.         }
  7227.         query << ") `T` WHERE `rn` > " << startPage << " AND `rn` <= " << endPage;
  7228.     } else if (type == HIGHSCORE_OURRANK) {
  7229.         std::string entriesStr = std::to_string(entriesPerPage);
  7230.         query << "SELECT *, @row AS `entries`, (@ourRow DIV " << entriesStr << ") + 1 AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn`, @ourRow := IF(`id` = " << player->getGUID() << ", @row - 1, @ourRow) AS `rw` FROM (SELECT `id`, `name`, `level`, `vocation`, `" << categoryName << "` AS `points`, @curRank := IF(@prevRank = `" << categoryName << "`, @curRank, IF(@prevRank := `" << categoryName << "`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0, @ourRow := 0) `r` WHERE `group_id` < " << static_cast<int>(account::GroupType::GROUP_TYPE_GAMEMASTER) << " ORDER BY `" << categoryName << "` DESC) `t`";
  7231.         if (vocation != 0xFFFFFFFF) {
  7232.             bool firstVocation = true;
  7233.  
  7234.             const auto& vocationsMap = g_vocations.getVocations();
  7235.             for (const auto& it : vocationsMap) {
  7236.                 const Vocation& voc = it.second;
  7237.                 if (voc.getFromVocation() == vocation) {
  7238.                     if (firstVocation) {
  7239.                         query << " WHERE `vocation` = " << voc.getId();
  7240.                         firstVocation = false;
  7241.                     } else {
  7242.                         query << " OR `vocation` = " << voc.getId();
  7243.                     }
  7244.                 }
  7245.             }
  7246.         }
  7247.         query << ") `T` WHERE `rn` > ((@ourRow DIV " << entriesStr << ") * " << entriesStr << ") AND `rn` <= (((@ourRow DIV " << entriesStr << ") * " << entriesStr << ") + " << entriesStr << ")";
  7248.     }
  7249.  
  7250.     uint32_t playerID = player->getID();
  7251.     std::function<void(DBResult_ptr, bool)> callback = [playerID, category, vocation, entriesPerPage](DBResult_ptr result, bool) {
  7252.         Player* player = g_game.getPlayerByID(playerID);
  7253.         if (!player) {
  7254.             return;
  7255.         }
  7256.  
  7257.         player->resetAsyncOngoingTask(PlayerAsyncTask_Highscore);
  7258.         if (!result) {
  7259.             player->sendHighscoresNoData();
  7260.             return;
  7261.         }
  7262.  
  7263.         uint16_t page = result->getNumber<uint16_t>("page");
  7264.         uint32_t pages = result->getNumber<uint32_t>("entries");
  7265.         pages += entriesPerPage - 1;
  7266.         pages /= entriesPerPage;
  7267.  
  7268.         std::vector<HighscoreCharacter> characters;
  7269.         characters.reserve(result->countResults());
  7270.         do {
  7271.             uint8_t characterVocation;
  7272.             Vocation* voc = g_vocations.getVocation(result->getNumber<uint16_t>("vocation"));
  7273.             if (voc) {
  7274.                 characterVocation = voc->getClientId();
  7275.             } else {
  7276.                 characterVocation = 0;
  7277.             }
  7278.             characters.emplace_back(std::move(result->getString("name")), result->getNumber<uint64_t>("points"), result->getNumber<uint32_t>("id"), result->getNumber<uint32_t>("rank"), result->getNumber<uint16_t>("level"), characterVocation);
  7279.         } while (result->next());
  7280.         player->sendHighscores(characters, category, vocation, page, static_cast<uint16_t>(pages));
  7281.     };
  7282.     g_databaseTasks.addTask(std::move(query.str()), callback, true);
  7283.     player->addAsyncOngoingTask(PlayerAsyncTask_Highscore);
  7284. }
  7285.  
  7286. void Game::playerTournamentLeaderboard(uint32_t playerId, uint8_t leaderboardType) {
  7287.     Player* player = getPlayerByID(playerId);
  7288.     if (!player || leaderboardType > 1) {
  7289.         return;
  7290.     }
  7291.  
  7292.     player->sendTournamentLeaderboard();
  7293. }
  7294.  
  7295. void Game::playerReportRuleViolationReport(uint32_t playerId, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation)
  7296. {
  7297.     Player* player = getPlayerByID(playerId);
  7298.     if (!player) {
  7299.         return;
  7300.     }
  7301.  
  7302.     g_events->eventPlayerOnReportRuleViolation(player, targetName, reportType, reportReason, comment, translation);
  7303. }
  7304.  
  7305. void Game::playerReportBug(uint32_t playerId, const std::string& message, const Position& position, uint8_t category)
  7306. {
  7307.     Player* player = getPlayerByID(playerId);
  7308.     if (!player) {
  7309.         return;
  7310.     }
  7311.  
  7312.     g_events->eventPlayerOnReportBug(player, message, position, category);
  7313. }
  7314.  
  7315. void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment)
  7316. {
  7317.     Player* player = getPlayerByID(playerId);
  7318.     if (!player) {
  7319.         return;
  7320.     }
  7321.  
  7322.     // TODO: move debug assertions to database
  7323.     FILE* file = fopen("client_assertions.txt", "a");
  7324.     if (file) {
  7325.         fprintf(file, "----- %s - %s (%s) -----\n", formatDate(time(nullptr)).c_str(), player->getName().c_str(), convertIPToString(player->getIP()).c_str());
  7326.         fprintf(file, "%s\n%s\n%s\n%s\n", assertLine.c_str(), date.c_str(), description.c_str(), comment.c_str());
  7327.         fclose(file);
  7328.     }
  7329. }
  7330.  
  7331. void Game::playerNpcGreet(uint32_t playerId, uint32_t npcId)
  7332. {
  7333.     Player* player = getPlayerByID(playerId);
  7334.     if (!player) {
  7335.         return;
  7336.     }
  7337.  
  7338.     Creature* creature = getCreatureByID(npcId);
  7339.     if (!creature) {
  7340.         return;
  7341.     }
  7342.  
  7343.     Npc* npc = creature->getNpc();
  7344.     if(npc) {
  7345.         SpectatorHashSet spectators;
  7346.         spectators.insert(npc);
  7347.         map.getSpectators(spectators, player->getPosition(), true, true);
  7348.         internalCreatureSay(player, TALKTYPE_SAY, "Hi", false, &spectators);
  7349.         spectators.clear();
  7350.         spectators.insert(npc);
  7351.         if (npc->getSpeechBubble() == SPEECHBUBBLE_TRADE) {
  7352.             internalCreatureSay(player, TALKTYPE_PRIVATE_PN, "Trade", false, &spectators);
  7353.         } else {
  7354.             internalCreatureSay(player, TALKTYPE_PRIVATE_PN, "Sail", false, &spectators);
  7355.         }
  7356.  
  7357.         return;
  7358.     }
  7359. }
  7360.  
  7361. void Game::playerLeaveMarket(uint32_t playerId)
  7362. {
  7363.     Player* player = getPlayerByID(playerId);
  7364.     if (!player) {
  7365.         return;
  7366.     }
  7367.  
  7368.     player->setInMarket(false);
  7369. }
  7370.  
  7371. void Game::playerBrowseMarket(uint32_t playerId, uint16_t spriteId)
  7372. {
  7373.     Player* player = getPlayerByID(playerId);
  7374.     if (!player) {
  7375.         return;
  7376.     }
  7377.  
  7378.     if (!player->isInMarket()) {
  7379.         return;
  7380.     }
  7381.  
  7382.     const ItemType& it = Item::items.getItemIdByClientId(spriteId);
  7383.     if (it.id == 0) {
  7384.         return;
  7385.     }
  7386.  
  7387.     if (it.wareId == 0) {
  7388.         return;
  7389.     }
  7390.  
  7391.     const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id);
  7392.     const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id);
  7393.     player->sendMarketBrowseItem(it.id, buyOffers, sellOffers);
  7394.     player->sendMarketDetail(it.id);
  7395. }
  7396.  
  7397. void Game::playerBrowseMarketOwnOffers(uint32_t playerId)
  7398. {
  7399.     Player* player = getPlayerByID(playerId);
  7400.     if (!player) {
  7401.         return;
  7402.     }
  7403.  
  7404.     if (!player->isInMarket()) {
  7405.         return;
  7406.     }
  7407.  
  7408.     const MarketOfferList& buyOffers = IOMarket::getOwnOffers(MARKETACTION_BUY, player->getGUID());
  7409.     const MarketOfferList& sellOffers = IOMarket::getOwnOffers(MARKETACTION_SELL, player->getGUID());
  7410.     player->sendMarketBrowseOwnOffers(buyOffers, sellOffers);
  7411. }
  7412.  
  7413. void Game::playerBrowseMarketOwnHistory(uint32_t playerId)
  7414. {
  7415.     Player* player = getPlayerByID(playerId);
  7416.     if (!player) {
  7417.         return;
  7418.     }
  7419.  
  7420.     if (!player->isInMarket()) {
  7421.         return;
  7422.     }
  7423.  
  7424.     const HistoryMarketOfferList& buyOffers = IOMarket::getOwnHistory(MARKETACTION_BUY, player->getGUID());
  7425.     const HistoryMarketOfferList& sellOffers = IOMarket::getOwnHistory(MARKETACTION_SELL, player->getGUID());
  7426.     player->sendMarketBrowseOwnHistory(buyOffers, sellOffers);
  7427. }
  7428.  
  7429. void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous) // Limit of 64k of items to create offer
  7430. {
  7431.     // 64000 is size of the client limitation
  7432.     if (amount == 0 || amount > 64000) {
  7433.         return;
  7434.     }
  7435.  
  7436.     if (price == 0 || price > 999999999) {
  7437.         return;
  7438.     }
  7439.  
  7440.     if (type != MARKETACTION_BUY && type != MARKETACTION_SELL) {
  7441.         return;
  7442.     }
  7443.  
  7444.     Player* player = getPlayerByID(playerId);
  7445.     if (!player) {
  7446.         return;
  7447.     }
  7448.  
  7449.     if (!player->isInMarket()) {
  7450.         return;
  7451.     }
  7452.  
  7453.     // Check market exhausted
  7454.     if (player->isMarketExhausted()) {
  7455.         player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
  7456.         g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
  7457.         return;
  7458.     }
  7459.  
  7460.     if (g_config.getBoolean(ConfigManager::MARKET_PREMIUM) && !player->isPremium()) {
  7461.         player->sendMarketLeave();
  7462.         return;
  7463.     }
  7464.  
  7465.     const ItemType& itt = Item::items.getItemIdByClientId(spriteId);
  7466.     if (itt.id == 0 || itt.wareId == 0) {
  7467.         return;
  7468.     }
  7469.  
  7470.     const ItemType& it = Item::items.getItemIdByClientId(itt.wareId);
  7471.     if (it.id == 0 || it.wareId == 0) {
  7472.         return;
  7473.     }
  7474.  
  7475.     if (!it.stackable && amount > 2000) {
  7476.         return;
  7477.     }
  7478.  
  7479.     const uint32_t maxOfferCount = g_config.getNumber(ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER);
  7480.     if (maxOfferCount != 0 && IOMarket::getPlayerOfferCount(player->getGUID()) >= maxOfferCount) {
  7481.         return;
  7482.     }
  7483.  
  7484.     uint64_t calcFee = (price / 100.) * amount;
  7485.     uint32_t minFee = std::min<uint32_t>(100000, calcFee);
  7486.     uint32_t fee = std::max<uint32_t>(20, minFee);
  7487.  
  7488.     if (type == MARKETACTION_SELL) {
  7489.  
  7490.         if (fee > (player->getBankBalance() + player->getMoney())) {
  7491.             return;
  7492.         }
  7493.  
  7494.         DepotLocker* depotLocker = player->getDepotLocker(player->getLastDepotId());
  7495.         if (!depotLocker) {
  7496.             return;
  7497.         }
  7498.  
  7499.         if (it.id == ITEM_STORE_COIN) {
  7500.       account::Account account(player->getAccount());
  7501.       account.LoadAccountDB();
  7502.       uint32_t coins;
  7503.       account.GetCoins(&coins);
  7504.  
  7505.       if (amount > coins) {
  7506.         return;
  7507.       }
  7508.       account.RemoveCoins(static_cast<uint32_t>(amount));
  7509.     } else {
  7510.         uint16_t stashmath = amount;
  7511.         uint16_t stashminus = player->getStashItemCount(it.wareId);
  7512.         if (stashminus > 0) {
  7513.             stashmath = (amount - (amount > stashminus ? stashminus : amount));
  7514.             player->withdrawItem(it.wareId, (amount > stashminus ? stashminus : amount));
  7515.         }
  7516.  
  7517.         std::forward_list<Item *> itemList = getMarketItemList(it.wareId, stashmath, depotLocker);
  7518.  
  7519.         if (!itemList.empty()) {
  7520.             if (it.stackable) {
  7521.                 uint16_t tmpAmount = stashmath;
  7522.                 for (Item *item : itemList) {
  7523.                     uint16_t removeCount = std::min<uint16_t>(tmpAmount, item->getItemCount());
  7524.                     tmpAmount -= removeCount;
  7525.                     internalRemoveItem(item, removeCount);
  7526.  
  7527.                 }
  7528.             } else {
  7529.                 for (Item *item : itemList) {
  7530.                     internalRemoveItem(item);
  7531.                 }
  7532.             }
  7533.         }
  7534.    }
  7535.     g_game.removeMoney(player, fee, 0, true);
  7536.     } else {
  7537.  
  7538.         uint64_t totalPrice = price * amount;
  7539.         totalPrice += fee;
  7540.         if (totalPrice > (player->getMoney() + player->getBankBalance())) {
  7541.             return;
  7542.         }
  7543.  
  7544.         g_game.removeMoney(player, totalPrice, 0, true);
  7545.     }
  7546.  
  7547.     IOMarket::createOffer(player->getGUID(), static_cast<MarketAction_t>(type), it.id, amount, price, anonymous);
  7548.  
  7549.     auto ColorItem = itemsPriceMap.find(it.id);
  7550.     if (ColorItem == itemsPriceMap.end()) {
  7551.         itemsPriceMap[it.id] = price;
  7552.         itemsSaleCount++;
  7553.     } else if (ColorItem->second < price) {
  7554.         itemsPriceMap[it.id] = price;
  7555.     }
  7556.  
  7557.     player->sendMarketEnter(player->getLastDepotId());
  7558.     const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id);
  7559.     const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id);
  7560.     player->sendMarketBrowseItem(it.id, buyOffers, sellOffers);
  7561.  
  7562.     //
  7563.     player->updateMarketExhausted(); // Exhausted for create offert in the market
  7564. }
  7565.  
  7566. void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter) // Market cancel offer
  7567. {
  7568.     Player* player = getPlayerByID(playerId);
  7569.     if (!player) {
  7570.         return;
  7571.     }
  7572.  
  7573.     if (!player->isInMarket()) {
  7574.         return;
  7575.     }
  7576.  
  7577.     // Check market exhausted
  7578.     if (player->isMarketExhausted()) {
  7579.         player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
  7580.         g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
  7581.         return;
  7582.     }
  7583.  
  7584.     MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter);
  7585.     if (offer.id == 0 || offer.playerId != player->getGUID()) {
  7586.         return;
  7587.     }
  7588.  
  7589.     if (offer.type == MARKETACTION_BUY) {
  7590.         player->setBankBalance( player->getBankBalance() + static_cast<uint64_t>(offer.price) * offer.amount);
  7591.         player->sendMarketEnter(player->getLastDepotId());
  7592.     } else {
  7593.         const ItemType& it = Item::items[offer.itemId];
  7594.         if (it.id == 0) {
  7595.             return;
  7596.         }
  7597.  
  7598.         if (it.id == ITEM_STORE_COIN) {
  7599.       account::Account account;
  7600.       account.LoadAccountDB(player->getAccount());
  7601.       account.AddCoins(offer.amount);
  7602.     }
  7603.         else if (it.stackable) {
  7604.             uint16_t tmpAmount = offer.amount;
  7605.             while (tmpAmount > 0) {
  7606.                 int32_t stackCount = std::min<int32_t>(100, tmpAmount);
  7607.                 Item* item = Item::CreateItem(it.id, stackCount);
  7608.                 if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
  7609.                     delete item;
  7610.                     break;
  7611.                 }
  7612.  
  7613.                 tmpAmount -= stackCount;
  7614.             }
  7615.         } else {
  7616.             int32_t subType;
  7617.             if (it.charges != 0) {
  7618.                 subType = it.charges;
  7619.             } else {
  7620.                 subType = -1;
  7621.             }
  7622.  
  7623.             for (uint16_t i = 0; i < offer.amount; ++i) {
  7624.                 Item* item = Item::CreateItem(it.id, subType);
  7625.                 if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
  7626.                     delete item;
  7627.                     break;
  7628.                 }
  7629.             }
  7630.         }
  7631.     }
  7632.  
  7633.     IOMarket::moveOfferToHistory(offer.id, OFFERSTATE_CANCELLED);
  7634.     offer.amount = 0;
  7635.     offer.timestamp += g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
  7636.     player->sendMarketCancelOffer(offer);
  7637.     player->sendMarketEnter(player->getLastDepotId());
  7638.     player->updateMarketExhausted(); // Exhausted for cancel offer in the market
  7639. }
  7640.  
  7641. void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount) // Limit of 64k of items to create offer
  7642. {
  7643.     if (amount == 0 || amount > 64000) {
  7644.         return;
  7645.     }
  7646.  
  7647.     Player* player = getPlayerByID(playerId);
  7648.     if (!player) {
  7649.         return;
  7650.     }
  7651.  
  7652.     if (!player->isInMarket()) {
  7653.         return;
  7654.     }
  7655.  
  7656.     // Check market exhausted
  7657.     if (player->isMarketExhausted()) {
  7658.         player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
  7659.         g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
  7660.         return;
  7661.     }
  7662.  
  7663.     MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter);
  7664.     if (offer.id == 0) {
  7665.         return;
  7666.     }
  7667.  
  7668.     if (amount > offer.amount) {
  7669.         return;
  7670.     }
  7671.  
  7672.     const ItemType& it = Item::items[offer.itemId];
  7673.     if (it.id == 0) {
  7674.         return;
  7675.     }
  7676.  
  7677.     uint64_t totalPrice = offer.price * amount;
  7678.  
  7679.     // The player has an offer to by something and someone is going to sell to it
  7680.     // so the market action is 'buy' as who created the offer is buying.
  7681.     if (offer.type == MARKETACTION_BUY) {
  7682.         DepotLocker* depotLocker = player->getDepotLocker(player->getLastDepotId());
  7683.         if (!depotLocker) {
  7684.             return;
  7685.         }
  7686.  
  7687.         Player* buyerPlayer = getPlayerByGUID(offer.playerId);
  7688.         if (player == buyerPlayer) {
  7689.             player->sendFYIBox("You cannot accept your own offer.");
  7690.             return;
  7691.         }
  7692.  
  7693.         if (!buyerPlayer) {
  7694.             buyerPlayer = new Player(nullptr);
  7695.             if (!IOLoginData::loadPlayerById(buyerPlayer, offer.playerId)) {
  7696.                 delete buyerPlayer;
  7697.                 return;
  7698.             }
  7699.         }
  7700.  
  7701.         if (it.id == ITEM_STORE_COIN) {
  7702.       account::Account account;
  7703.       account.LoadAccountDB(player->getAccount());
  7704.       uint32_t coins;
  7705.       account.GetCoins(&coins);
  7706.       if (amount > coins)
  7707.       {
  7708.         return;
  7709.       }
  7710.  
  7711.       account.RemoveCoins(amount);
  7712.       account.RegisterCoinsTransaction(account::COIN_REMOVE, amount,
  7713.                                       "Sold on Market");
  7714.     } else {
  7715.             std::forward_list<Item*> itemList = getMarketItemList(it.wareId, amount, depotLocker);
  7716.             if (itemList.empty()) {
  7717.                 return;
  7718.             }
  7719.  
  7720.             if (it.stackable) {
  7721.                 uint16_t tmpAmount = amount;
  7722.                 for (Item* item : itemList) {
  7723.                     uint16_t removeCount = std::min<uint16_t>(tmpAmount, item->getItemCount());
  7724.                     tmpAmount -= removeCount;
  7725.                     internalRemoveItem(item, removeCount);
  7726.  
  7727.                     if (tmpAmount == 0) {
  7728.                         break;
  7729.                     }
  7730.                 }
  7731.             } else {
  7732.                 for (Item* item : itemList) {
  7733.                     internalRemoveItem(item);
  7734.                 }
  7735.             }
  7736.         }
  7737.         player->setBankBalance(player->getBankBalance() + totalPrice);
  7738.  
  7739.         if (it.id == ITEM_STORE_COIN) {
  7740.       account::Account account;
  7741.       account.LoadAccountDB(buyerPlayer->getAccount());
  7742.       account.AddCoins(amount);
  7743.       account.RegisterCoinsTransaction(account::COIN_ADD, amount,
  7744.                                       "Purchased on Market");
  7745.     }
  7746.     else if (it.stackable)
  7747.     {
  7748.       uint16_t tmpAmount = amount;
  7749.             while (tmpAmount > 0) {
  7750.                 uint16_t stackCount = std::min<uint16_t>(100, tmpAmount);
  7751.                 Item* item = Item::CreateItem(it.id, stackCount);
  7752.                 if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
  7753.                     delete item;
  7754.                     break;
  7755.                 }
  7756.  
  7757.                 tmpAmount -= stackCount;
  7758.             }
  7759.     }
  7760.     else
  7761.     {
  7762.       int32_t subType;
  7763.             if (it.charges != 0) {
  7764.                 subType = it.charges;
  7765.             } else {
  7766.                 subType = -1;
  7767.             }
  7768.  
  7769.             for (uint16_t i = 0; i < amount; ++i) {
  7770.                 Item* item = Item::CreateItem(it.id, subType);
  7771.                 if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
  7772.                     delete item;
  7773.                     break;
  7774.                 }
  7775.             }
  7776.     }
  7777.  
  7778.     if (buyerPlayer->isOffline()) {
  7779.             IOLoginData::savePlayer(buyerPlayer);
  7780.             delete buyerPlayer;
  7781.         }
  7782.     } else {//MARKETACTION_SELL
  7783.         Player* sellerPlayer = getPlayerByGUID(offer.playerId);
  7784.         if (player == sellerPlayer) {
  7785.             player->sendFYIBox("You cannot accept your own offer.");
  7786.             return;
  7787.         }
  7788.  
  7789.         if (totalPrice > (player->getBankBalance() + player->getMoney())) {
  7790.             return;
  7791.         }
  7792.  
  7793.         // Have enough money on the bank
  7794.         if(totalPrice <= player->getBankBalance())
  7795.         {
  7796.             player->setBankBalance(player->getBankBalance() - totalPrice);
  7797.         }
  7798.         else
  7799.         {
  7800.             uint64_t remainsPrice = 0;
  7801.             remainsPrice = totalPrice - player->getBankBalance();
  7802.             player->setBankBalance(0);
  7803.             g_game.removeMoney(player, remainsPrice);
  7804.         }
  7805.  
  7806.         if (it.id == ITEM_STORE_COIN) {
  7807.       account::Account account;
  7808.       account.LoadAccountDB(player->getAccount());
  7809.       account.AddCoins(amount);
  7810.       account.RegisterCoinsTransaction(account::COIN_ADD, amount,
  7811.                                       "Purchased on Market");
  7812.         } else if (it.stackable) {
  7813.             uint16_t tmpAmount = amount;
  7814.             while (tmpAmount > 0) {
  7815.                 uint16_t stackCount = std::min<uint16_t>(100, tmpAmount);
  7816.                 Item* item = Item::CreateItem(it.id, stackCount);
  7817.                 if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
  7818.                     delete item;
  7819.                     break;
  7820.                 }
  7821.  
  7822.                 tmpAmount -= stackCount;
  7823.             }
  7824.         } else {
  7825.             int32_t subType;
  7826.             if (it.charges != 0) {
  7827.                 subType = it.charges;
  7828.             } else {
  7829.                 subType = -1;
  7830.             }
  7831.  
  7832.             for (uint16_t i = 0; i < amount; ++i) {
  7833.                 Item* item = Item::CreateItem(it.id, subType);
  7834.                 if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
  7835.                     delete item;
  7836.                     break;
  7837.                 }
  7838.             }
  7839.         }
  7840.  
  7841.         if (sellerPlayer) {
  7842.             sellerPlayer->setBankBalance(sellerPlayer->getBankBalance() + totalPrice);
  7843.             if (it.id == ITEM_STORE_COIN) {
  7844.         account::Account account;
  7845.         account.LoadAccountDB(sellerPlayer->getAccount());
  7846.         account.RegisterCoinsTransaction(account::COIN_REMOVE, amount,
  7847.                                         "Sold on Market");
  7848.       }
  7849.         } else {
  7850.             IOLoginData::increaseBankBalance(offer.playerId, totalPrice);
  7851.             if (it.id == ITEM_STORE_COIN) {
  7852.                 sellerPlayer = new Player(nullptr);
  7853.  
  7854.                 if (IOLoginData::loadPlayerById(sellerPlayer, offer.playerId)) {
  7855.           account::Account account;
  7856.           account.LoadAccountDB(sellerPlayer->getAccount());
  7857.           account.RegisterCoinsTransaction(account::COIN_REMOVE, amount,
  7858.                                           "Sold on Market");
  7859.         }
  7860.  
  7861.                 delete sellerPlayer;
  7862.             }
  7863.         }
  7864.         if (it.id != ITEM_STORE_COIN) {
  7865.         player->onReceiveMail();
  7866.         }
  7867.     }
  7868.  
  7869.     const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
  7870.  
  7871.     IOMarket::appendHistory(player->getGUID(), (offer.type == MARKETACTION_BUY ? MARKETACTION_SELL : MARKETACTION_BUY), offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTEDEX);
  7872.  
  7873.     IOMarket::appendHistory(offer.playerId, offer.type, offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTED);
  7874.  
  7875.     offer.amount -= amount;
  7876.  
  7877.     if (offer.amount == 0) {
  7878.         IOMarket::deleteOffer(offer.id);
  7879.     } else {
  7880.         IOMarket::acceptOffer(offer.id, amount);
  7881.     }
  7882.  
  7883.     player->sendMarketEnter(player->getLastDepotId());
  7884.     offer.timestamp += marketOfferDuration;
  7885.     player->sendMarketAcceptOffer(offer);
  7886.  
  7887.     player->updateMarketExhausted(); // Exhausted for accept offer in the market
  7888. }
  7889.  
  7890. void Game::playerStoreOpen(uint32_t playerId, uint8_t serviceType)
  7891. {
  7892.     Player* player = getPlayerByID(playerId);
  7893.     if (player) {
  7894.         player->sendOpenStore(serviceType);
  7895.     }
  7896. }
  7897.  
  7898. void Game::playerShowStoreCategoryOffers(uint32_t playerId, StoreCategory* category)
  7899. {
  7900.     Player* player = getPlayerByID(playerId);
  7901.     if (player) {
  7902.         player->sendShowStoreCategoryOffers(category);
  7903.     }
  7904. }
  7905.  
  7906. void Game::playerBuyStoreOffer(uint32_t playerId, uint32_t offerId, uint8_t productType, const std::string& additionalInfo /* ="" */)
  7907. {
  7908.     Player* player = getPlayerByID(playerId);
  7909.     if (player) {
  7910.         const BaseOffer* offer = gameStore.getOfferByOfferId(offerId);
  7911.  
  7912.         if (offer == nullptr || offer->type == DISABLED) {
  7913.             player->sendStoreError(STORE_ERROR_NETWORK, "The offer is either fake or corrupt.");
  7914.             return;
  7915.         }
  7916.  
  7917.     account::Account account;
  7918.     account.LoadAccountDB(player->getAccount());
  7919.     uint32_t coins;
  7920.     account.GetCoins(&coins);
  7921.     if (coins < offer->price) //player doesnt have enough coins
  7922.         {
  7923.             player->sendStoreError(STORE_ERROR_PURCHASE, "You don't have enough coins");
  7924.             return;
  7925.         }
  7926.  
  7927.         std::stringstream message;
  7928.         if (offer->type == ITEM || offer->type == STACKABLE_ITEM || offer->type == WRAP_ITEM) {
  7929.             const ItemOffer* tmp = (ItemOffer*) offer;
  7930.  
  7931.             message << "You have purchased " << tmp->count << "x " << offer->name << " for " << offer->price << " coins.";
  7932.  
  7933.             Thing* thing = player->getThing(CONST_SLOT_STORE_INBOX);
  7934.             if (thing == nullptr) {
  7935.                 player->sendStoreError(STORE_ERROR_NETWORK, "We cannot locate your store inbox, try again after relog and if this error persists, contact the system administrator.");
  7936.                 return;
  7937.             }
  7938.  
  7939.             Container* inbox = thing->getItem()->getContainer();  // TODO: Not the right way to get the storeInbox
  7940.             if (!inbox) {
  7941.                 player->sendStoreError(STORE_ERROR_NETWORK, "We cannot locate your store inbox, try again after relog and if this error persists, contact the system administrator.");
  7942.                 return;
  7943.             }
  7944.  
  7945.             uint32_t freeSlots = inbox->capacity() - inbox->size();
  7946.             uint32_t requiredSlots = (tmp->type == ITEM || tmp->type == WRAP_ITEM) ? tmp->count : (tmp->count%100)? (uint32_t)(tmp->count/100)+1 :(uint32_t) tmp->count/100;
  7947.             uint32_t capNeeded = (tmp->type == WRAP_ITEM)?0:Item::items[tmp->productId].weight * tmp->count;
  7948.             if (freeSlots < requiredSlots ) {
  7949.                 player->sendStoreError(STORE_ERROR_PURCHASE, "Insuficient free slots in your store inbox.");
  7950.                 return;
  7951.             } else if (player->getFreeCapacity()< capNeeded) {
  7952.                 player->sendStoreError(STORE_ERROR_PURCHASE, "Not enough cap to carry.");
  7953.                 return;
  7954.             } else {
  7955.                 uint16_t pendingCount = tmp->count;
  7956.                 uint8_t packSize = (offer->type == STACKABLE_ITEM) ? 100 : 1;
  7957.         account.LoadAccountDB(player->getAccount());
  7958.         account.RemoveCoins(offer->price);
  7959.         account.RegisterCoinsTransaction(account::COIN_REMOVE, offer->price,
  7960.                                         offer->name);
  7961.                 while(pendingCount>0)
  7962.                 {
  7963.                     Item* item;
  7964.  
  7965.                     if (offer->type == WRAP_ITEM) {
  7966.                         item = Item::CreateItem(TRANSFORM_BOX_ID, std::min<uint16_t>(packSize, pendingCount));
  7967.                         item->setActionId(tmp->productId);
  7968.                         item->setSpecialDescription("Unwrap it in your own house to create a <" + Item::items[tmp->productId].name + ">.");
  7969.                     } else {
  7970.                         item = Item::CreateItem(tmp->productId, std::min<uint16_t>(packSize, pendingCount));
  7971.                     }
  7972.  
  7973.                     if (internalAddItem(inbox, item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
  7974.                         delete item;
  7975.                         player->sendStoreError(STORE_ERROR_PURCHASE, "We couldn't deliver all the items.\nOnly the delivered ones were charged from you account");
  7976.             account.AddCoins((offer->price * (tmp->count - pendingCount)/tmp->count));
  7977.             account.RegisterCoinsTransaction(account::COIN_REMOVE,
  7978.                   offer->price + (offer->price * (tmp->count - pendingCount))/tmp->count,
  7979.                   offer->name);
  7980.                         return;
  7981.                     }
  7982.                     pendingCount-= std::min<uint16_t>(pendingCount,packSize);
  7983.                 }
  7984.  
  7985.         account.GetCoins(&coins);
  7986.         player->sendStorePurchaseSuccessful(message.str(), coins);
  7987.         return;
  7988.             }
  7989.         } else if (offer->type == OUTFIT || offer->type == OUTFIT_ADDON) {
  7990.             const OutfitOffer* outfitOffer = (OutfitOffer*) offer;
  7991.  
  7992.             uint16_t looktype = (player->getSex()==PLAYERSEX_MALE)? outfitOffer->maleLookType : outfitOffer->femaleLookType;
  7993.             uint8_t addons = outfitOffer->addonNumber;
  7994.  
  7995.             if (!player->canWear(looktype, addons)) {
  7996.                 player->addOutfit(looktype, addons);
  7997.         account.LoadAccountDB(player->getAccount());
  7998.         account.RemoveCoins(offer->price);
  7999.         account.RegisterCoinsTransaction(account::COIN_REMOVE, offer->price,
  8000.                                         offer->name);
  8001.                 message<< "You've successfully bought the "<< outfitOffer->name << ".";
  8002.         account.GetCoins(&coins);
  8003.         player->sendStorePurchaseSuccessful(message.str(), coins);
  8004.         return;
  8005.             } else {
  8006.                 player->sendStoreError(STORE_ERROR_NETWORK, "This outfit seems not to suit you well, we are sorry for that!");
  8007.                 return;
  8008.             }
  8009.         } else if (offer->type == MOUNT) {
  8010.             const MountOffer* mntOffer = (MountOffer*) offer;
  8011.             const Mount* mount = mounts.getMountByID(mntOffer->mountId);
  8012.             if (player->hasMount(mount)) {
  8013.                 player->sendStoreError(STORE_ERROR_PURCHASE, "You arealdy own this mount.");
  8014.                 return;
  8015.             } else {
  8016.         account.LoadAccountDB(player->getAccount());
  8017.         account.RemoveCoins(mntOffer->price);
  8018.         if (!player->tameMount(mount->id)) {
  8019.           account.AddCoins(mntOffer->price);
  8020.           player->sendStoreError(STORE_ERROR_PURCHASE, "An error ocurred processing your purchase. Try again later.");
  8021.                     return;
  8022.                 } else {
  8023.           account.RegisterCoinsTransaction(account::COIN_REMOVE, offer->price,
  8024.                                           offer->name);
  8025.                     message << "You've successfully bought the " << mount->name <<" Mount.";
  8026.           account.GetCoins(&coins);
  8027.           player->sendStorePurchaseSuccessful(message.str(), coins);
  8028.           return;
  8029.                 }
  8030.             }
  8031.         } else if (offer->type == NAMECHANGE) {
  8032.             if (productType == SIMPLE) { //client didn't sent the new name yet, request additionalInfo
  8033.                 player->sendStoreRequestAdditionalInfo(offer->id, ADDITIONALINFO);
  8034.                 return;
  8035.             } else {
  8036.                 Database &db = Database::getInstance();
  8037.                 std::ostringstream query;
  8038.                 std::string newName = additionalInfo;
  8039.                 trimString(newName);
  8040.  
  8041.                 query << "SELECT `id` FROM `players` WHERE `name`=" << db.escapeString(newName);
  8042.                 if (db.storeQuery(query.str())) { //name already in use
  8043.                     message << "This name is already in use.";
  8044.                     player->sendStoreError(STORE_ERROR_PURCHASE, message.str());
  8045.                     return;
  8046.                 } else {
  8047.                     query.str("");
  8048.                     toLowerCaseString(newName);
  8049.  
  8050.                     std::string responseMessage;
  8051.                     NameEval_t nameValidation = validateName(newName);
  8052.  
  8053.                     switch (nameValidation) {
  8054.                         case INVALID_LENGTH:
  8055.                             responseMessage = "Your new name must be more than 3 and less than 14 characters long.";
  8056.                             break;
  8057.                         case INVALID_TOKEN_LENGTH:
  8058.                             responseMessage = "Every words of your new name must be at least 2 characters long.";
  8059.                             break;
  8060.                         case INVALID_FORBIDDEN:
  8061.                             responseMessage = "You're using forbidden words in your new name.";
  8062.                             break;
  8063.                         case INVALID_CHARACTER:
  8064.                             responseMessage = "Your new name contains invalid characters.";
  8065.                             break;
  8066.                         case INVALID:
  8067.                             responseMessage = "Your new name is invalid.";
  8068.                             break;
  8069.                         case VALID:
  8070.                             responseMessage = "You have successfully changed you name, you must relog to see changes.";
  8071.                             break;
  8072.                     }
  8073.  
  8074.                     if (nameValidation != VALID) { //invalid name typed
  8075.                         player->sendStoreError(STORE_ERROR_PURCHASE, responseMessage);
  8076.                         return;
  8077.                     } else { //valid name so far
  8078.  
  8079.                         //check if it's an NPC or Monster name.
  8080.  
  8081.                         if (g_monsters.getMonsterType(newName)) {
  8082.                             responseMessage = "Your new name cannot be a monster's name.";
  8083.                             player->sendStoreError(STORE_ERROR_PURCHASE, responseMessage);
  8084.                             return;
  8085.                         } else if (getNpcByName(newName)) {
  8086.                             responseMessage = "Your new name cannot be an NPC's name.";
  8087.                             player->sendStoreError(STORE_ERROR_PURCHASE, responseMessage);
  8088.                             return;
  8089.                         } else {
  8090.                             capitalizeWords(newName);
  8091.  
  8092.                             query << "UPDATE `players` SET `name` = " << db.escapeString(newName) << " WHERE `id` = "
  8093.                                   << player->getGUID();
  8094.                             if (db.executeQuery(query.str())) {
  8095.                 account.LoadAccountDB(player->getAccount());
  8096.                 account.RemoveCoins(offer->price);
  8097.                 account.RegisterCoinsTransaction(account::COIN_REMOVE,
  8098.                                                 offer->price, offer->name);
  8099.                 account.GetCoins(&coins);
  8100.                 message << "You have successfully changed you name, you must relog to see the changes.";
  8101.                 player->sendStorePurchaseSuccessful(message.str(), coins);
  8102.                                 return;
  8103.                             } else {
  8104.                                 message << "An error ocurred processing your request, no changes were made.";
  8105.                                 player->sendStoreError(STORE_ERROR_PURCHASE, message.str());
  8106.                                 return;
  8107.                             }
  8108.                         }
  8109.                     }
  8110.                 }
  8111.             }
  8112.         } else if (offer->type == SEXCHANGE) {
  8113.             PlayerSex_t playerSex = player->getSex();
  8114.             Outfit_t playerOutfit = player->getCurrentOutfit();
  8115.  
  8116.             message << "Your character is now ";
  8117.  
  8118.             for(auto outfit : player->outfits) { //adding all outfits of the oposite sex.
  8119.                 const Outfit* opositeSexOutfit = Outfits::getInstance().getOpositeSexOutfitByLookType(playerSex, outfit.lookType);
  8120.  
  8121.                 if (opositeSexOutfit) {
  8122.                     player->addOutfit(opositeSexOutfit->lookType, 0);//since addons could have different recipes, we can't add automatically
  8123.                 }
  8124.             }
  8125.  
  8126.             if (playerSex == PLAYERSEX_FEMALE) {
  8127.                 player->setSex(PLAYERSEX_MALE);
  8128.                 playerOutfit.lookType=128; //default citizen
  8129.                 playerOutfit.lookAddons=0;
  8130.  
  8131.                 message << "male.";
  8132.             } else {//player is male
  8133.                 player->setSex(PLAYERSEX_FEMALE);
  8134.                 playerOutfit.lookType=136; //default citizen
  8135.                 playerOutfit.lookAddons=0;
  8136.                 message << "female.";
  8137.             }
  8138.             playerChangeOutfit(player->getID(),playerOutfit);
  8139.             // TODO: add the other sex equivalent outfits player already have in the current sex.
  8140.       account.LoadAccountDB(player->getAccount());
  8141.       account.RemoveCoins(offer->price);
  8142.       account.RegisterCoinsTransaction(account::COIN_REMOVE, offer->price,
  8143.                                       offer->name);
  8144.       account.GetCoins(&coins);
  8145.             player->sendStorePurchaseSuccessful(message.str(), coins);
  8146.             return;
  8147.         } else if (offer->type == PROMOTION) {
  8148.             if (player->isPremium() && !player->isPromoted()) {
  8149.                 uint16_t promotedId = g_vocations.getPromotedVocation(player->getVocation()->getId());
  8150.  
  8151.                 if (promotedId == VOCATION_NONE || promotedId == player->getVocation()->getId()) {
  8152.                     player->sendStoreError(STORE_ERROR_PURCHASE, "Your character cannot be promoted.");
  8153.                     return;
  8154.                 } else {
  8155.           account.LoadAccountDB(player->getAccount());
  8156.           account.RemoveCoins(offer->price);
  8157.           account.RegisterCoinsTransaction(account::COIN_REMOVE,
  8158.                                           offer->price, offer->name);
  8159.           account.GetCoins(&coins);
  8160.                     player->setVocation(promotedId);
  8161.                     player->addStorageValue(STORAGEVALUE_PROMOTION,1);
  8162.                     message << "You've been promoted! Relog to see the changes.";
  8163.                     player->sendStorePurchaseSuccessful(message.str(), coins);
  8164.                     return;
  8165.                 }
  8166.             } else {
  8167.                 player->sendStoreError(STORE_ERROR_PURCHASE, "Your character cannot be promoted.");
  8168.                 return;
  8169.             }
  8170.         } else if (offer->type == PREMIUM_TIME) {
  8171.             PremiumTimeOffer* premiumTimeOffer = (PremiumTimeOffer*) offer;
  8172.       account.LoadAccountDB(player->getAccount());
  8173.       account.RemoveCoins(offer->price);
  8174.       account.RegisterCoinsTransaction(account::COIN_REMOVE, offer->price,
  8175.                                       offer->name);
  8176.       account.GetCoins(&coins);
  8177.             player->setPremiumDays(player->premiumDays+premiumTimeOffer->days);
  8178.             IOLoginData::addPremiumDays(player->getAccount(),premiumTimeOffer->days);
  8179.             message<< "You've successfully bought "<< premiumTimeOffer->days << " days of premium time.";
  8180.             player->sendStorePurchaseSuccessful(message.str(),coins);
  8181.             return;
  8182.         } else if (offer->type == TELEPORT) {
  8183.             TeleportOffer* tpOffer = (TeleportOffer*) offer;
  8184.             if (player->canLogout()) {
  8185.                 Position toPosition;
  8186.                 Position fromPosition = player->getPosition();
  8187.                 if (tpOffer->position.x == 0 || tpOffer->position.y == 0 || tpOffer->position.z == 0) { //temple teleport
  8188.                     toPosition=player->getTemplePosition();
  8189.                 } else {
  8190.                     toPosition = tpOffer->position;
  8191.                 }
  8192.  
  8193.                 ReturnValue returnValue = internalTeleport(player, toPosition, false);
  8194.                 if (returnValue!=RETURNVALUE_NOERROR) {
  8195.                     player->sendStoreError(STORE_ERROR_PURCHASE, "Your character cannot be teleported there at the moment.");
  8196.                     return;
  8197.                 } else {
  8198.           account.LoadAccountDB(player->getAccount());
  8199.           account.RemoveCoins(offer->price);
  8200.           account.RegisterCoinsTransaction(account::COIN_REMOVE, offer->price,
  8201.                                           offer->name);
  8202.           account.GetCoins(&coins);
  8203.                     addMagicEffect(fromPosition, CONST_ME_POFF);
  8204.                     addMagicEffect(toPosition,CONST_ME_TELEPORT);
  8205.                     player->sendStorePurchaseSuccessful("You've successfully been teleported.", coins);
  8206.                     return;
  8207.                 }
  8208.             } else {
  8209.                 player->sendStoreError(STORE_ERROR_PURCHASE, "Your character has some teleportation block at the moment and cannot be teleported.");
  8210.                 return;
  8211.             }
  8212.         } else if (offer->type == BLESSING) {
  8213.             BlessingOffer* blessingOffer = (BlessingOffer*) offer;
  8214.  
  8215.             uint8_t blessingsToAdd = 0;
  8216.             for(uint8_t bless : blessingOffer->blessings) {
  8217.                 if (player->hasBlessing(bless)) {//player already has this bless
  8218.                     message << "Your character already has ";
  8219.                     message << ((blessingOffer->blessings.size() >1)? "one or more of these blessings." : "this bless.");
  8220.  
  8221.                     player->sendStoreError(STORE_ERROR_PURCHASE, message.str());
  8222.                     return;
  8223.                 }
  8224.                 blessingsToAdd = bless;
  8225.             }
  8226.       account.LoadAccountDB(player->getAccount());
  8227.       account.RemoveCoins(offer->price);
  8228.       account.RegisterCoinsTransaction(account::COIN_REMOVE, offer->price,
  8229.                                       offer->name);
  8230.       account.GetCoins(&coins);
  8231.             player->addBlessing(blessingsToAdd, 1);
  8232.             message<< "You've successfully bought the "<< offer->name << ".";
  8233.             player->sendStorePurchaseSuccessful(message.str(), coins);
  8234.             return;
  8235.         } else {
  8236.             // TODO: BOOST_XP and BOOST_STAMINA (the support systems are not yet implemented)
  8237.             player->sendStoreError(STORE_ERROR_INFORMATION, "JLCVP: NOT YET IMPLEMENTED!");
  8238.             return;
  8239.         }
  8240.     }
  8241. }
  8242.  
  8243. void Game::playerCoinTransfer(uint32_t playerId, const std::string &receiverName, uint32_t amount)
  8244. {
  8245.     Player* sender = getPlayerByID(playerId);
  8246.     Player* receiver = getPlayerByName(receiverName);
  8247.   std::stringstream message;
  8248.     if (!sender) {
  8249.         return;
  8250.     } else if (!receiver) {
  8251.       message << "Player \"" << receiverName << "\" doesn't exist.";
  8252.             sender->sendStoreError(STORE_ERROR_TRANSFER, message.str());
  8253.             return;
  8254.     } else {
  8255.  
  8256.     account::Account sender_account;
  8257.     sender_account.LoadAccountDB(sender->getAccount());
  8258.     account::Account receiver_account;
  8259.     receiver_account.LoadAccountDB(receiver->getAccount());
  8260.     uint32_t sender_coins;
  8261.     sender_account.GetCoins(&sender_coins);
  8262.  
  8263.     if (sender->getAccount() == receiver->getAccount()) {  //sender and receiver are the same
  8264.       message << "You cannot send coins to your own account.";
  8265.       sender->sendStoreError(STORE_ERROR_TRANSFER, message.str());
  8266.       return;
  8267.     } else if (sender_coins < amount) {
  8268.       message << "You don't have enough funds to transfer these coins.";
  8269.       sender->sendStoreError(STORE_ERROR_TRANSFER, message.str());
  8270.       return;
  8271.     } else {
  8272.  
  8273.       sender_account.RemoveCoins(amount);
  8274.       receiver_account.AddCoins(amount);
  8275.       message << "Transfered to " << receiverName;
  8276.       sender_account.RegisterCoinsTransaction(account::COIN_REMOVE, amount,
  8277.                                               message.str());
  8278.  
  8279.       message.str("");
  8280.       message << "Received from" << sender->name;
  8281.       receiver_account.RegisterCoinsTransaction(account::COIN_REMOVE,
  8282.                                                 amount, message.str());
  8283.  
  8284.       sender_account.GetCoins(&sender_coins);
  8285.       message.str("");
  8286.       message << "You have successfully transfered " << amount << " coins to " << receiverName << ".";
  8287.       sender->sendStorePurchaseSuccessful(message.str(), sender_coins);
  8288.       if (receiver && !receiver->isOffline()) {
  8289.         receiver->sendCoinBalance();
  8290.       }
  8291.     }
  8292.   }
  8293. }
  8294.  
  8295. void Game::playerStoreTransactionHistory(uint32_t playerId, uint32_t page)
  8296. {
  8297.     Player* player = getPlayerByID(playerId);
  8298.     if (player) {
  8299.         HistoryStoreOfferList list = IOGameStore::getHistoryEntries(player->getAccount(),page);
  8300.         if (!list.empty()) {
  8301.             player->sendStoreTrasactionHistory(list, page, GameStore::HISTORY_ENTRIES_PER_PAGE);
  8302.         } else {
  8303.             player->sendStoreError(STORE_ERROR_HISTORY, "You don't have any entries yet.");
  8304.         }
  8305.     }
  8306. }
  8307.  
  8308. void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer)
  8309. {
  8310.     Player* player = getPlayerByID(playerId);
  8311.     if (!player) {
  8312.         return;
  8313.     }
  8314.  
  8315.     for (CreatureEvent* creatureEvent : player->getCreatureEvents(CREATURE_EVENT_EXTENDED_OPCODE)) {
  8316.         creatureEvent->executeExtendedOpcode(player, opcode, buffer);
  8317.     }
  8318. }
  8319.  
  8320. std::forward_list<Item*> Game::getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotLocker* depotLocker)
  8321. {
  8322.     std::forward_list<Item*> itemList;
  8323.     uint16_t count = 0;
  8324.  
  8325.     std::list<Container*> containers {depotLocker};
  8326.     do {
  8327.         Container* container = containers.front();
  8328.         containers.pop_front();
  8329.  
  8330.         for (Item* item : container->getItemList()) {
  8331.             Container* c = item->getContainer();
  8332.             if (c && !c->empty()) {
  8333.                 containers.push_back(c);
  8334.                 continue;
  8335.             }
  8336.  
  8337.             const ItemType& itemType = Item::items[item->getID()];
  8338.             if (itemType.wareId != wareId) {
  8339.                 continue;
  8340.             }
  8341.  
  8342.             if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) {
  8343.                 continue;
  8344.             }
  8345.  
  8346.             if (!item->hasMarketAttributes()) {
  8347.                 continue;
  8348.             }
  8349.  
  8350.             itemList.push_front(item);
  8351.  
  8352.             count += Item::countByType(item, -1);
  8353.             if (count >= sufficientCount) {
  8354.                 return itemList;
  8355.             }
  8356.         }
  8357.     } while (!containers.empty());
  8358.     return std::forward_list<Item*>();
  8359. }
  8360.  
  8361. void Game::forceAddCondition(uint32_t creatureId, Condition* condition)
  8362. {
  8363.     Creature* creature = getCreatureByID(creatureId);
  8364.     if (!creature) {
  8365.         delete condition;
  8366.         return;
  8367.     }
  8368.  
  8369.     creature->addCondition(condition, true);
  8370. }
  8371.  
  8372. void Game::forceRemoveCondition(uint32_t creatureId, ConditionType_t type)
  8373. {
  8374.     Creature* creature = getCreatureByID(creatureId);
  8375.     if (!creature) {
  8376.         return;
  8377.     }
  8378.  
  8379.     creature->removeCondition(type, true);
  8380. }
  8381.  
  8382. void Game::sendOfflineTrainingDialog(Player* player)
  8383. {
  8384.     if (!player) {
  8385.         return;
  8386.     }
  8387.  
  8388.     if (!player->hasModalWindowOpen(offlineTrainingWindow.id)) {
  8389.         player->sendModalWindow(offlineTrainingWindow);
  8390.     }
  8391. }
  8392.  
  8393. void Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice)
  8394. {
  8395.     Player* player = getPlayerByID(playerId);
  8396.     if (!player) {
  8397.         return;
  8398.     }
  8399.  
  8400.     if (!player->hasModalWindowOpen(modalWindowId)) {
  8401.         return;
  8402.     }
  8403.  
  8404.     player->onModalWindowHandled(modalWindowId);
  8405.  
  8406.     // offline training, hardcoded
  8407.     if (modalWindowId == std::numeric_limits<uint32_t>::max()) {
  8408.         if (button == 1) {
  8409.             if (choice == SKILL_SWORD || choice == SKILL_AXE || choice == SKILL_CLUB || choice == SKILL_DISTANCE || choice == SKILL_MAGLEVEL) {
  8410.                 BedItem* bedItem = player->getBedItem();
  8411.                 if (bedItem && bedItem->sleep(player)) {
  8412.                     player->setOfflineTrainingSkill(choice);
  8413.                     return;
  8414.                 }
  8415.             }
  8416.         } else {
  8417.             player->sendTextMessage(MESSAGE_EVENT_ADVANCE, "Offline training aborted.");
  8418.         }
  8419.  
  8420.         player->setBedItem(nullptr);
  8421.     } else {
  8422.         for (auto creatureEvent : player->getCreatureEvents(CREATURE_EVENT_MODALWINDOW)) {
  8423.             creatureEvent->executeModalWindow(player, modalWindowId, button, choice);
  8424.         }
  8425.     }
  8426. }
  8427.  
  8428. void Game::updatePlayerSaleItems(uint32_t playerId)
  8429. {
  8430.     Player* player = getPlayerByID(playerId);
  8431.     if (!player) {
  8432.         return;
  8433.     }
  8434.  
  8435.   std::map<uint32_t, uint32_t> tempInventoryMap;
  8436.   player->getAllItemTypeCountAndSubtype(tempInventoryMap);
  8437.  
  8438.     player->sendSaleItemList(tempInventoryMap);
  8439.     player->setScheduledSaleUpdate(false);
  8440. }
  8441.  
  8442. void Game::addPlayer(Player* player)
  8443. {
  8444.     const std::string& lowercase_name = asLowerCaseString(player->getName());
  8445.     mappedPlayerNames[lowercase_name] = player;
  8446.     wildcardTree.insert(lowercase_name);
  8447.     players[player->getID()] = player;
  8448. }
  8449.  
  8450. void Game::removePlayer(Player* player)
  8451. {
  8452.     const std::string& lowercase_name = asLowerCaseString(player->getName());
  8453.     mappedPlayerNames.erase(lowercase_name);
  8454.     wildcardTree.remove(lowercase_name);
  8455.     players.erase(player->getID());
  8456. }
  8457.  
  8458. void Game::addNpc(Npc* npc)
  8459. {
  8460.     npcs[npc->getID()] = npc;
  8461. }
  8462.  
  8463. void Game::removeNpc(Npc* npc)
  8464. {
  8465.     npcs.erase(npc->getID());
  8466. }
  8467.  
  8468. void Game::addMonster(Monster* monster)
  8469. {
  8470.     monsters[monster->getID()] = monster;
  8471. }
  8472.  
  8473. void Game::removeMonster(Monster* monster)
  8474. {
  8475.     monsters.erase(monster->getID());
  8476. }
  8477.  
  8478. Guild* Game::getGuild(uint32_t id) const
  8479. {
  8480.     auto it = guilds.find(id);
  8481.     if (it == guilds.end()) {
  8482.         return nullptr;
  8483.     }
  8484.     return it->second;
  8485. }
  8486.  
  8487. void Game::addGuild(Guild* guild)
  8488. {
  8489.   if (!guild) {
  8490.     return;
  8491.   }
  8492.     guilds[guild->getId()] = guild;
  8493. }
  8494.  
  8495. void Game::removeGuild(uint32_t guildId)
  8496. {
  8497.   auto it = guilds.find(guildId);
  8498.   if (it != guilds.end()) {
  8499.     IOGuild::saveGuild(it->second);
  8500.   }
  8501.     guilds.erase(guildId);
  8502. }
  8503.  
  8504. void Game::decreaseBrowseFieldRef(const Position& pos)
  8505. {
  8506.     Tile* tile = map.getTile(pos.x, pos.y, pos.z);
  8507.     if (!tile) {
  8508.         return;
  8509.     }
  8510.  
  8511.     auto it = browseFields.find(tile);
  8512.     if (it != browseFields.end()) {
  8513.         it->second->decrementReferenceCounter();
  8514.     }
  8515. }
  8516.  
  8517. void Game::internalRemoveItems(std::vector<Item*> itemList, uint32_t amount, bool stackable)
  8518. {
  8519.     if (stackable) {
  8520.         for (Item* item : itemList) {
  8521.             if (item->getItemCount() > amount) {
  8522.                 internalRemoveItem(item, amount);
  8523.                 break;
  8524.             } else {
  8525.                 amount -= item->getItemCount();
  8526.                 internalRemoveItem(item);
  8527.             }
  8528.         }
  8529.     } else {
  8530.         for (Item* item : itemList) {
  8531.             internalRemoveItem(item);
  8532.         }
  8533.     }
  8534. }
  8535.  
  8536. BedItem* Game::getBedBySleeper(uint32_t guid) const
  8537. {
  8538.     auto it = bedSleepersMap.find(guid);
  8539.     if (it == bedSleepersMap.end()) {
  8540.         return nullptr;
  8541.     }
  8542.     return it->second;
  8543. }
  8544.  
  8545. void Game::setBedSleeper(BedItem* bed, uint32_t guid)
  8546. {
  8547.     bedSleepersMap[guid] = bed;
  8548. }
  8549.  
  8550. void Game::removeBedSleeper(uint32_t guid)
  8551. {
  8552.     auto it = bedSleepersMap.find(guid);
  8553.     if (it != bedSleepersMap.end()) {
  8554.         bedSleepersMap.erase(it);
  8555.     }
  8556. }
  8557.  
  8558. Item* Game::getUniqueItem(uint16_t uniqueId)
  8559. {
  8560.     auto it = uniqueItems.find(uniqueId);
  8561.     if (it == uniqueItems.end()) {
  8562.         return nullptr;
  8563.     }
  8564.     return it->second;
  8565. }
  8566.  
  8567. bool Game::addUniqueItem(uint16_t uniqueId, Item* item)
  8568. {
  8569.     auto result = uniqueItems.emplace(uniqueId, item);
  8570.     if (!result.second) {
  8571.         SPDLOG_WARN("Duplicate unique id: {}", uniqueId);
  8572.     }
  8573.     return result.second;
  8574. }
  8575.  
  8576. void Game::removeUniqueItem(uint16_t uniqueId)
  8577. {
  8578.     auto it = uniqueItems.find(uniqueId);
  8579.     if (it != uniqueItems.end()) {
  8580.         uniqueItems.erase(it);
  8581.     }
  8582. }
  8583.  
  8584. bool Game::reload(ReloadTypes_t reloadType)
  8585. {
  8586.     switch (reloadType) {
  8587.         case RELOAD_TYPE_MONSTERS: {
  8588.             g_scripts->loadScripts("monster", false, true);
  8589.             return true;
  8590.         }
  8591.         case RELOAD_TYPE_CHAT: return g_chat->load();
  8592.         case RELOAD_TYPE_CONFIG: return g_config.reload();
  8593.         case RELOAD_TYPE_EVENTS: return g_events->loadFromXml();
  8594.         case RELOAD_TYPE_ITEMS: return Item::items.reload();
  8595.         case RELOAD_TYPE_MODULES: return g_modules->reload();
  8596.         case RELOAD_TYPE_MOUNTS: return mounts.reload();
  8597.         case RELOAD_TYPE_IMBUEMENTS: return g_imbuements->reload();
  8598.         case RELOAD_TYPE_NPCS: {
  8599.             Npcs::reload();
  8600.             return true;
  8601.         }
  8602.  
  8603.         case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup();
  8604.  
  8605.         case RELOAD_TYPE_SPELLS: {
  8606.             if (!g_spells->reload()) {
  8607.                 SPDLOG_WARN("[Game::reload] - Failed to reload spells.");
  8608.                 std::terminate();
  8609.             }
  8610.             return true;
  8611.         }
  8612.  
  8613.         case RELOAD_TYPE_SCRIPTS: {
  8614.             // commented out stuff is TODO, once we approach further in revscriptsys
  8615.             g_actions->clear(true);
  8616.             g_creatureEvents->clear(true);
  8617.             g_moveEvents->clear(true);
  8618.             g_talkActions->clear(true);
  8619.             g_globalEvents->clear(true);
  8620.             g_weapons->clear(true);
  8621.             g_weapons->loadDefaults();
  8622.             g_spells->clear(true);
  8623.             g_scripts->loadScripts("scripts", false, true);
  8624.             return true;
  8625.         }
  8626.  
  8627.         default: {
  8628.             if (!g_spells->reload()) {
  8629.                 SPDLOG_WARN("[Game::reload] - Failed to reload spells.");
  8630.                 std::terminate();
  8631.             }
  8632.  
  8633.             g_config.reload();
  8634.             Npcs::reload();
  8635.             raids.reload() && raids.startup();
  8636.             Item::items.reload();
  8637.             g_weapons->clear(true);
  8638.             g_weapons->loadDefaults();
  8639.             mounts.reload();
  8640.             g_events->loadFromXml();
  8641.             g_chat->load();
  8642.             g_actions->clear(true);
  8643.             g_creatureEvents->clear(true);
  8644.             g_moveEvents->clear(true);
  8645.             g_talkActions->clear(true);
  8646.             g_globalEvents->clear(true);
  8647.             g_spells->clear(true);
  8648.             g_scripts->loadScripts("scripts", false, true);
  8649.             return true;
  8650.         }
  8651.     }
  8652.     return true;
  8653. }
  8654.  
  8655. bool Game::itemidHasMoveevent(uint32_t itemid)
  8656. {
  8657.     return g_moveEvents->isRegistered(itemid);
  8658. }
  8659.  
  8660. bool Game::hasEffect(uint8_t effectId) {
  8661.     for (uint8_t i = CONST_ME_NONE; i <= CONST_ME_LAST; i++) {
  8662.         MagicEffectClasses effect = static_cast<MagicEffectClasses>(i);
  8663.         if (effect == effectId) {
  8664.             return true;
  8665.         }
  8666.     }
  8667.     return false;
  8668. }
  8669.  
  8670. bool Game::hasDistanceEffect(uint8_t effectId) {
  8671.     for (uint8_t i = CONST_ANI_NONE; i <= CONST_ANI_LAST; i++) {
  8672.         ShootType_t effect = static_cast<ShootType_t>(i);
  8673.         if (effect == effectId) {
  8674.             return true;
  8675.         }
  8676.     }
  8677.     return false;
  8678. }
  8679.  
Add Comment
Please, Sign In to add comment