Guest User

Untitled

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