Advertisement
DrakensOT

player.cpp

May 4th, 2023
261
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 115.54 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 "bed.h"
  23. #include "chat.h"
  24. #include "combat.h"
  25. #include "configmanager.h"
  26. #include "creatureevent.h"
  27. #include "events.h"
  28. #include "game.h"
  29. #include "iologindata.h"
  30. #include "monster.h"
  31. #include "movement.h"
  32. #include "scheduler.h"
  33. #include "weapons.h"
  34.  
  35. #include <fmt/format.h>
  36.  
  37. extern ConfigManager g_config;
  38. extern Game g_game;
  39. extern Chat* g_chat;
  40. extern Vocations g_vocations;
  41. extern MoveEvents* g_moveEvents;
  42. extern Weapons* g_weapons;
  43. extern CreatureEvents* g_creatureEvents;
  44. extern Events* g_events;
  45.  
  46. MuteCountMap Player::muteCountMap;
  47.  
  48. uint32_t Player::playerAutoID = 0x10000000;
  49.  
  50. Player::Player(ProtocolGame_ptr p) :
  51. Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), /*inbox(new Inbox(ITEM_INBOX)), storeInbox(new StoreInbox(ITEM_STORE_INBOX)),*/ client(std::move(p))
  52. {
  53. /*inbox->incrementReferenceCounter();
  54.  
  55. storeInbox->setParent(this);
  56. storeInbox->incrementReferenceCounter();*/
  57. }
  58.  
  59. Player::~Player()
  60. {
  61. for (Item* item : inventory) {
  62. if (item) {
  63. item->setParent(nullptr);
  64. item->decrementReferenceCounter();
  65. }
  66. }
  67.  
  68. /*for (const auto& it : depotLockerMap) {
  69. it.second->removeInbox(inbox);
  70. }
  71.  
  72. inbox->decrementReferenceCounter();
  73.  
  74. storeInbox->setParent(nullptr);
  75. storeInbox->decrementReferenceCounter();*/
  76.  
  77. setWriteItem(nullptr);
  78. setEditHouse(nullptr);
  79. }
  80.  
  81. bool Player::setVocation(uint16_t vocId)
  82. {
  83. Vocation* voc = g_vocations.getVocation(vocId);
  84. if (!voc) {
  85. return false;
  86. }
  87. vocation = voc;
  88.  
  89. updateRegeneration();
  90. return true;
  91. }
  92.  
  93. bool Player::isPushable() const
  94. {
  95. if (hasFlag(PlayerFlag_CannotBePushed)) {
  96. return false;
  97. }
  98. return Creature::isPushable();
  99. }
  100.  
  101. std::string Player::getDescription(int32_t lookDistance) const
  102. {
  103. std::ostringstream s;
  104.  
  105. if (lookDistance == -1) {
  106. s << "yourself.";
  107.  
  108. if (group->access) {
  109. s << " You are " << group->name << '.';
  110. } else if (vocation->getId() != VOCATION_NONE) {
  111. s << " You are " << vocation->getVocDescription() << '.';
  112. } else {
  113. s << " You have no vocation.";
  114. }
  115. } else {
  116. s << name;
  117. if (!group->access) {
  118. s << " (Level " << level << ')';
  119. }
  120. s << '.';
  121.  
  122. if (sex == PLAYERSEX_FEMALE) {
  123. s << " She";
  124. } else {
  125. s << " He";
  126. }
  127.  
  128. if (group->access) {
  129. s << " is " << group->name << '.';
  130. } else if (vocation->getId() != VOCATION_NONE) {
  131. s << " is " << vocation->getVocDescription() << '.';
  132. } else {
  133. s << " has no vocation.";
  134. }
  135. }
  136.  
  137. if (party) {
  138. if (lookDistance == -1) {
  139. s << " Your party has ";
  140. } else if (sex == PLAYERSEX_FEMALE) {
  141. s << " She is in a party with ";
  142. } else {
  143. s << " He is in a party with ";
  144. }
  145.  
  146. size_t memberCount = party->getMemberCount() + 1;
  147. if (memberCount == 1) {
  148. s << "1 member and ";
  149. } else {
  150. s << memberCount << " members and ";
  151. }
  152.  
  153. size_t invitationCount = party->getInvitationCount();
  154. if (invitationCount == 1) {
  155. s << "1 pending invitation.";
  156. } else {
  157. s << invitationCount << " pending invitations.";
  158. }
  159. }
  160.  
  161. if (!guild || !guildRank) {
  162. return s.str();
  163. }
  164.  
  165. if (lookDistance == -1) {
  166. s << " You are ";
  167. } else if (sex == PLAYERSEX_FEMALE) {
  168. s << " She is ";
  169. } else {
  170. s << " He is ";
  171. }
  172.  
  173. s << guildRank->name << " of the " << guild->getName();
  174. if (!guildNick.empty()) {
  175. s << " (" << guildNick << ')';
  176. }
  177.  
  178. size_t memberCount = guild->getMemberCount();
  179. if (memberCount == 1) {
  180. s << ", which has 1 member, " << guild->getMembersOnline().size() << " of them online.";
  181. } else {
  182. s << ", which has " << memberCount << " members, " << guild->getMembersOnline().size() << " of them online.";
  183. }
  184. return s.str();
  185. }
  186.  
  187. Item* Player::getInventoryItem(slots_t slot) const
  188. {
  189. if (slot < CONST_SLOT_FIRST || slot > CONST_SLOT_LAST) {
  190. return nullptr;
  191. }
  192. return inventory[slot];
  193. }
  194.  
  195. void Player::addConditionSuppressions(uint32_t conditions)
  196. {
  197. conditionSuppressions |= conditions;
  198. }
  199.  
  200. void Player::removeConditionSuppressions(uint32_t conditions)
  201. {
  202. conditionSuppressions &= ~conditions;
  203. }
  204.  
  205. Item* Player::getWeapon(slots_t slot, bool ignoreAmmo) const
  206. {
  207. Item* item = inventory[slot];
  208. if (!item) {
  209. return nullptr;
  210. }
  211.  
  212. WeaponType_t weaponType = item->getWeaponType();
  213. if (weaponType == WEAPON_NONE || weaponType == WEAPON_SHIELD || weaponType == WEAPON_AMMO) {
  214. return nullptr;
  215. }
  216.  
  217. if (!ignoreAmmo && weaponType == WEAPON_DISTANCE) {
  218. const ItemType& it = Item::items[item->getID()];
  219. if (it.ammoType != AMMO_NONE) {
  220. Item* ammoItem = inventory[CONST_SLOT_AMMO];
  221. if (!ammoItem || ammoItem->getAmmoType() != it.ammoType) {
  222. return nullptr;
  223. }
  224. item = ammoItem;
  225. }
  226. }
  227. return item;
  228. }
  229.  
  230. Item* Player::getWeapon(bool ignoreAmmo/* = false*/) const
  231. {
  232. Item* item = getWeapon(CONST_SLOT_LEFT, ignoreAmmo);
  233. if (item) {
  234. return item;
  235. }
  236.  
  237. item = getWeapon(CONST_SLOT_RIGHT, ignoreAmmo);
  238. if (item) {
  239. return item;
  240. }
  241. return nullptr;
  242. }
  243.  
  244. WeaponType_t Player::getWeaponType() const
  245. {
  246. Item* item = getWeapon();
  247. if (!item) {
  248. return WEAPON_NONE;
  249. }
  250. return item->getWeaponType();
  251. }
  252.  
  253. int32_t Player::getWeaponSkill(const Item* item) const
  254. {
  255. if (!item) {
  256. return getSkillLevel(SKILL_FIST);
  257. }
  258.  
  259. int32_t attackSkill;
  260.  
  261. WeaponType_t weaponType = item->getWeaponType();
  262. switch (weaponType) {
  263. case WEAPON_SWORD: {
  264. attackSkill = getSkillLevel(SKILL_SWORD);
  265. break;
  266. }
  267.  
  268. case WEAPON_CLUB: {
  269. attackSkill = getSkillLevel(SKILL_CLUB);
  270. break;
  271. }
  272.  
  273. case WEAPON_AXE: {
  274. attackSkill = getSkillLevel(SKILL_AXE);
  275. break;
  276. }
  277.  
  278. case WEAPON_DISTANCE: {
  279. attackSkill = getSkillLevel(SKILL_DISTANCE);
  280. break;
  281. }
  282.  
  283. default: {
  284. attackSkill = 0;
  285. break;
  286. }
  287. }
  288. return attackSkill;
  289. }
  290.  
  291. int32_t Player::getArmor() const
  292. {
  293. int32_t armor = 0;
  294.  
  295. static const slots_t armorSlots[] = {CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING};
  296. for (slots_t slot : armorSlots) {
  297. Item* inventoryItem = inventory[slot];
  298. if (inventoryItem) {
  299. armor += inventoryItem->getArmor();
  300. }
  301. }
  302. return static_cast<int32_t>(armor * vocation->armorMultiplier);
  303. }
  304.  
  305. void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const
  306. {
  307. shield = nullptr;
  308. weapon = nullptr;
  309.  
  310. for (uint32_t slot = CONST_SLOT_RIGHT; slot <= CONST_SLOT_LEFT; slot++) {
  311. Item* item = inventory[slot];
  312. if (!item) {
  313. continue;
  314. }
  315.  
  316. switch (item->getWeaponType()) {
  317. case WEAPON_NONE:
  318. break;
  319.  
  320. case WEAPON_SHIELD: {
  321. if (!shield || item->getDefense() > shield->getDefense()) {
  322. shield = item;
  323. }
  324. break;
  325. }
  326.  
  327. default: { // weapons that are not shields
  328. weapon = item;
  329. break;
  330. }
  331. }
  332. }
  333. }
  334.  
  335. int32_t Player::getDefense() const
  336. {
  337. int32_t defenseSkill = getSkillLevel(SKILL_FIST);
  338. int32_t defenseValue = 7;
  339. const Item* weapon;
  340. const Item* shield;
  341. getShieldAndWeapon(shield, weapon);
  342.  
  343. if (weapon) {
  344. defenseValue = weapon->getDefense() + weapon->getExtraDefense();
  345. defenseSkill = getWeaponSkill(weapon);
  346. }
  347.  
  348. if (shield) {
  349. defenseValue = weapon != nullptr ? shield->getDefense() + weapon->getExtraDefense() : shield->getDefense();
  350. defenseSkill = getSkillLevel(SKILL_SHIELD);
  351. }
  352.  
  353. if (defenseSkill == 0) {
  354. switch (fightMode) {
  355. case FIGHTMODE_ATTACK:
  356. case FIGHTMODE_BALANCED:
  357. return 1;
  358.  
  359. case FIGHTMODE_DEFENSE:
  360. return 2;
  361. }
  362. }
  363.  
  364. return (defenseSkill / 4. + 2.23) * defenseValue * 0.15 * getDefenseFactor() * vocation->defenseMultiplier;
  365. }
  366.  
  367. uint32_t Player::getAttackSpeed() const
  368. {
  369. const Item* weapon = getWeapon(true);
  370. if (!weapon || weapon->getAttackSpeed() == 0) {
  371. return vocation->getAttackSpeed();
  372. }
  373.  
  374. return weapon->getAttackSpeed();
  375. }
  376.  
  377. float Player::getAttackFactor() const
  378. {
  379. switch (fightMode) {
  380. case FIGHTMODE_ATTACK: return 1.0f;
  381. case FIGHTMODE_BALANCED: return 1.2f;
  382. case FIGHTMODE_DEFENSE: return 2.0f;
  383. default: return 1.0f;
  384. }
  385. }
  386.  
  387. float Player::getDefenseFactor() const
  388. {
  389. switch (fightMode) {
  390. case FIGHTMODE_ATTACK: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.5f : 1.0f;
  391. case FIGHTMODE_BALANCED: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.75f : 1.0f;
  392. case FIGHTMODE_DEFENSE: return 1.0f;
  393. default: return 1.0f;
  394. }
  395. }
  396.  
  397. uint16_t Player::getClientIcons() const
  398. {
  399. uint16_t icons = 0;
  400. for (Condition* condition : conditions) {
  401. if (!isSuppress(condition->getType())) {
  402. icons |= condition->getIcons();
  403. }
  404. }
  405.  
  406. if (pzLocked) {
  407. icons |= ICON_REDSWORDS;
  408. }
  409.  
  410. /*
  411. if (tile && tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
  412. icons |= ICON_PIGEON;
  413.  
  414. // Don't show ICON_SWORDS if player is in protection zone.
  415. if (hasBitSet(ICON_SWORDS, icons)) {
  416. icons &= ~ICON_SWORDS;
  417. }
  418. }*/
  419.  
  420. // Game client debugs with 10 or more icons
  421. // so let's prevent that from happening.
  422. std::bitset<20> icon_bitset(static_cast<uint64_t>(icons));
  423. for (size_t pos = 0, bits_set = icon_bitset.count(); bits_set >= 10; ++pos) {
  424. if (icon_bitset[pos]) {
  425. icon_bitset.reset(pos);
  426. --bits_set;
  427. }
  428. }
  429. return icon_bitset.to_ulong();
  430. }
  431.  
  432. void Player::updateInventoryWeight()
  433. {
  434. if (hasFlag(PlayerFlag_HasInfiniteCapacity)) {
  435. return;
  436. }
  437.  
  438. inventoryWeight = 0;
  439. for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
  440. const Item* item = inventory[i];
  441. if (item) {
  442. inventoryWeight += item->getWeight();
  443. }
  444. }
  445.  
  446. if (StoreInbox* storeInbox = getStoreInbox()) {
  447. inventoryWeight += storeInbox->getWeight();
  448. }
  449. }
  450.  
  451. void Player::addSkillAdvance(skills_t skill, uint64_t count)
  452. {
  453. uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level);
  454. uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1);
  455. if (currReqTries >= nextReqTries) {
  456. //player has reached max skill
  457. return;
  458. }
  459.  
  460. g_events->eventPlayerOnGainSkillTries(this, skill, count);
  461. if (count == 0) {
  462. return;
  463. }
  464.  
  465. bool sendUpdateSkills = false;
  466. while ((skills[skill].tries + count) >= nextReqTries) {
  467. count -= nextReqTries - skills[skill].tries;
  468. skills[skill].level++;
  469. skills[skill].tries = 0;
  470. skills[skill].percent = 0;
  471.  
  472. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You advanced to {:s} level {:d}.", getSkillName(skill), skills[skill].level));
  473.  
  474. g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level);
  475.  
  476. sendUpdateSkills = true;
  477. currReqTries = nextReqTries;
  478. nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1);
  479. if (currReqTries >= nextReqTries) {
  480. count = 0;
  481. break;
  482. }
  483. }
  484.  
  485. skills[skill].tries += count;
  486.  
  487. uint32_t newPercent;
  488. if (nextReqTries > currReqTries) {
  489. newPercent = Player::getPercentLevel(skills[skill].tries, nextReqTries);
  490. } else {
  491. newPercent = 0;
  492. }
  493.  
  494. if (skills[skill].percent != newPercent) {
  495. skills[skill].percent = newPercent;
  496. sendUpdateSkills = true;
  497. }
  498.  
  499. if (sendUpdateSkills) {
  500. sendSkills();
  501. }
  502. }
  503.  
  504. void Player::removeSkillTries(skills_t skill, uint64_t count, bool notify/* = false*/)
  505. {
  506. uint16_t oldLevel = skills[skill].level;
  507. uint8_t oldPercent = skills[skill].percent;
  508.  
  509. while (count > skills[skill].tries) {
  510. count -= skills[skill].tries;
  511.  
  512. if (skills[skill].level <= MINIMUM_SKILL_LEVEL) {
  513. skills[skill].level = MINIMUM_SKILL_LEVEL;
  514. skills[skill].tries = 0;
  515. count = 0;
  516. break;
  517. }
  518.  
  519. skills[skill].tries = vocation->getReqSkillTries(skill, skills[skill].level);
  520. skills[skill].level--;
  521. }
  522.  
  523. skills[skill].tries = std::max<int32_t>(0, skills[skill].tries - count);
  524. skills[skill].percent = Player::getPercentLevel(skills[skill].tries, vocation->getReqSkillTries(skill, skills[skill].level));
  525.  
  526. if (notify) {
  527. bool sendUpdateSkills = false;
  528. if (oldLevel != skills[skill].level) {
  529. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You were downgraded to {:s} level {:d}.", getSkillName(skill), skills[skill].level));
  530. sendUpdateSkills = true;
  531. }
  532.  
  533. if (sendUpdateSkills || oldPercent != skills[skill].percent) {
  534. sendSkills();
  535. }
  536. }
  537. }
  538.  
  539. void Player::setVarStats(stats_t stat, int32_t modifier)
  540. {
  541. varStats[stat] += modifier;
  542.  
  543. switch (stat) {
  544. case STAT_MAXHITPOINTS: {
  545. if (getHealth() > getMaxHealth()) {
  546. Creature::changeHealth(getMaxHealth() - getHealth());
  547. } else {
  548. g_game.addCreatureHealth(this);
  549. }
  550. break;
  551. }
  552.  
  553. case STAT_MAXMANAPOINTS: {
  554. if (getMana() > getMaxMana()) {
  555. changeMana(getMaxMana() - getMana());
  556. }
  557. break;
  558. }
  559.  
  560. default: {
  561. break;
  562. }
  563. }
  564. }
  565.  
  566. int32_t Player::getDefaultStats(stats_t stat) const
  567. {
  568. switch (stat) {
  569. case STAT_MAXHITPOINTS: return healthMax;
  570. case STAT_MAXMANAPOINTS: return manaMax;
  571. case STAT_MAGICPOINTS: return getBaseMagicLevel();
  572. default: return 0;
  573. }
  574. }
  575.  
  576. void Player::addContainer(uint8_t cid, Container* container)
  577. {
  578. if (cid > 0xF) {
  579. return;
  580. }
  581.  
  582. /*if (container->getID() == ITEM_BROWSEFIELD) {
  583. container->incrementReferenceCounter();
  584. }*/
  585.  
  586. auto it = openContainers.find(cid);
  587. if (it != openContainers.end()) {
  588. OpenContainer& openContainer = it->second;
  589. /*Container* oldContainer = openContainer.container;
  590. if (oldContainer->getID() == ITEM_BROWSEFIELD) {
  591. oldContainer->decrementReferenceCounter();
  592. }*/
  593.  
  594. openContainer.container = container;
  595. openContainer.index = 0;
  596. } else {
  597. OpenContainer openContainer;
  598. openContainer.container = container;
  599. openContainer.index = 0;
  600. openContainers[cid] = openContainer;
  601. }
  602. }
  603.  
  604. void Player::closeContainer(uint8_t cid)
  605. {
  606. auto it = openContainers.find(cid);
  607. if (it == openContainers.end()) {
  608. return;
  609. }
  610.  
  611. //OpenContainer openContainer = it->second;
  612. //Container* container = openContainer.container;
  613. openContainers.erase(it);
  614.  
  615. /*if (container && container->getID() == ITEM_BROWSEFIELD) {
  616. container->decrementReferenceCounter();
  617. }*/
  618. }
  619.  
  620. void Player::setContainerIndex(uint8_t cid, uint16_t index)
  621. {
  622. auto it = openContainers.find(cid);
  623. if (it == openContainers.end()) {
  624. return;
  625. }
  626. it->second.index = index;
  627. }
  628.  
  629. Container* Player::getContainerByID(uint8_t cid)
  630. {
  631. auto it = openContainers.find(cid);
  632. if (it == openContainers.end()) {
  633. return nullptr;
  634. }
  635. return it->second.container;
  636. }
  637.  
  638. int8_t Player::getContainerID(const Container* container) const
  639. {
  640. for (const auto& it : openContainers) {
  641. if (it.second.container == container) {
  642. return it.first;
  643. }
  644. }
  645. return -1;
  646. }
  647.  
  648. uint16_t Player::getContainerIndex(uint8_t cid) const
  649. {
  650. auto it = openContainers.find(cid);
  651. if (it == openContainers.end()) {
  652. return 0;
  653. }
  654. return it->second.index;
  655. }
  656.  
  657. bool Player::canOpenCorpse(uint32_t ownerId) const
  658. {
  659. return getID() == ownerId || (party && party->canOpenCorpse(ownerId));
  660. }
  661.  
  662. uint16_t Player::getLookCorpse() const
  663. {
  664. if (sex == PLAYERSEX_FEMALE) {
  665. return ITEM_FEMALE_CORPSE;
  666. }
  667. return ITEM_MALE_CORPSE;
  668. }
  669.  
  670. void Player::addStorageValue(const uint32_t key, const int32_t value, const bool isLogin/* = false*/)
  671. {
  672. if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) {
  673. if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) {
  674. outfits.emplace_back(
  675. value >> 16,
  676. value & 0xFF
  677. );
  678. return;
  679. } else if (IS_IN_KEYRANGE(key, MOUNTS_RANGE)) {
  680. // do nothing
  681. } else {
  682. std::cout << "Warning: unknown reserved key: " << key << " player: " << getName() << std::endl;
  683. return;
  684. }
  685. }
  686.  
  687. if (value != -1) {
  688. int32_t oldValue;
  689. getStorageValue(key, oldValue);
  690.  
  691. storageMap[key] = value;
  692.  
  693. if (!isLogin) {
  694. auto currentFrameTime = g_dispatcher.getDispatcherCycle();
  695. if (lastQuestlogUpdate != currentFrameTime && g_game.quests.isQuestStorage(key, value, oldValue)) {
  696. lastQuestlogUpdate = currentFrameTime;
  697. sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your questlog has been updated.");
  698. }
  699. }
  700. } else {
  701. storageMap.erase(key);
  702. }
  703. }
  704.  
  705. bool Player::getStorageValue(const uint32_t key, int32_t& value) const
  706. {
  707. auto it = storageMap.find(key);
  708. if (it == storageMap.end()) {
  709. value = -1;
  710. return false;
  711. }
  712.  
  713. value = it->second;
  714. return true;
  715. }
  716.  
  717. bool Player::canSee(const Position& pos) const
  718. {
  719. if (!client) {
  720. return false;
  721. }
  722. return client->canSee(pos);
  723. }
  724.  
  725. bool Player::canSeeCreature(const Creature* creature) const
  726. {
  727. if (creature == this) {
  728. return true;
  729. }
  730.  
  731. if (creature->isInGhostMode() && !canSeeGhostMode(creature)) {
  732. return false;
  733. }
  734.  
  735. if (!creature->getPlayer() && !canSeeInvisibility() && creature->isInvisible()) {
  736. return false;
  737. }
  738. return true;
  739. }
  740.  
  741. bool Player::canSeeGhostMode(const Creature*) const
  742. {
  743. return group->access;
  744. }
  745.  
  746. bool Player::canWalkthrough(const Creature* creature) const
  747. {
  748. if (group->access || creature->isInGhostMode()) {
  749. return true;
  750. }
  751.  
  752. const Player* player = creature->getPlayer();
  753. if (!player || !g_config.getBoolean(ConfigManager::ALLOW_WALKTHROUGH)) {
  754. return false;
  755. }
  756.  
  757. const Tile* playerTile = player->getTile();
  758. if (!playerTile || (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && player->getLevel() > static_cast<uint32_t>(g_config.getNumber(ConfigManager::PROTECTION_LEVEL)))) {
  759. return false;
  760. }
  761.  
  762. const Item* playerTileGround = playerTile->getGround();
  763. if (!playerTileGround || !playerTileGround->hasWalkStack()) {
  764. return false;
  765. }
  766.  
  767. Player* thisPlayer = const_cast<Player*>(this);
  768. if ((OTSYS_TIME() - lastWalkthroughAttempt) > 2000) {
  769. thisPlayer->setLastWalkthroughAttempt(OTSYS_TIME());
  770. return false;
  771. }
  772.  
  773. if (creature->getPosition() != lastWalkthroughPosition) {
  774. thisPlayer->setLastWalkthroughPosition(creature->getPosition());
  775. return false;
  776. }
  777.  
  778. thisPlayer->setLastWalkthroughPosition(creature->getPosition());
  779. return true;
  780. }
  781.  
  782. bool Player::canWalkthroughEx(const Creature* creature) const
  783. {
  784. if (group->access) {
  785. return true;
  786. }
  787.  
  788. const Player* player = creature->getPlayer();
  789. if (!player || !g_config.getBoolean(ConfigManager::ALLOW_WALKTHROUGH)) {
  790. return false;
  791. }
  792.  
  793. const Tile* playerTile = player->getTile();
  794. return playerTile && (playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || player->getLevel() <= static_cast<uint32_t>(g_config.getNumber(ConfigManager::PROTECTION_LEVEL)));
  795. }
  796.  
  797. void Player::onReceiveMail() const
  798. {
  799. if (isNearDepotBox()) {
  800. sendTextMessage(MESSAGE_EVENT_ADVANCE, "New mail has arrived.");
  801. }
  802. }
  803.  
  804. bool Player::isNearDepotBox() const
  805. {
  806. const Position& pos = getPosition();
  807. for (int32_t cx = -1; cx <= 1; ++cx) {
  808. for (int32_t cy = -1; cy <= 1; ++cy) {
  809. Tile* tile = g_game.map.getTile(pos.x + cx, pos.y + cy, pos.z);
  810. if (!tile) {
  811. continue;
  812. }
  813.  
  814. if (tile->hasFlag(TILESTATE_DEPOT)) {
  815. return true;
  816. }
  817. }
  818. }
  819. return false;
  820. }
  821.  
  822. DepotChest* Player::getDepotChest(uint32_t depotId, bool autoCreate)
  823. {
  824. auto it = depotChests.find(depotId);
  825. if (it != depotChests.end()) {
  826. return it->second;
  827. }
  828.  
  829. if (!autoCreate) {
  830. return nullptr;
  831. }
  832.  
  833. it = depotChests.emplace(depotId, new DepotChest(ITEM_DEPOT)).first;
  834. it->second->setMaxDepotItems(getMaxDepotItems());
  835. return it->second;
  836. }
  837.  
  838. DepotLocker* Player::getDepotLocker(uint32_t depotId)
  839. {
  840. auto it = depotLockerMap.find(depotId);
  841. if (it != depotLockerMap.end()) {
  842. //inbox->setParent(it->second.get());
  843. return it->second.get();
  844. }
  845.  
  846. it = depotLockerMap.emplace(depotId, new DepotLocker(ITEM_LOCKER1)).first;
  847. it->second->setDepotId(depotId);
  848. //it->second->internalAddThing(Item::CreateItem(ITEM_MARKET));
  849. //it->second->internalAddThing(inbox);
  850. it->second->internalAddThing(getDepotChest(depotId, true));
  851. return it->second.get();
  852. }
  853.  
  854. void Player::sendCancelMessage(ReturnValue message) const
  855. {
  856. sendCancelMessage(getReturnMessage(message));
  857. }
  858.  
  859. void Player::sendStats()
  860. {
  861. if (client) {
  862. client->sendStats();
  863. lastStatsTrainingTime = getOfflineTrainingTime() / 60 / 1000;
  864. }
  865. }
  866.  
  867. void Player::sendPing()
  868. {
  869. int64_t timeNow = OTSYS_TIME();
  870.  
  871. bool hasLostConnection = false;
  872. if ((timeNow - lastPing) >= 5000) {
  873. lastPing = timeNow;
  874. if (client) {
  875. client->sendPing();
  876. } else {
  877. hasLostConnection = true;
  878. }
  879. }
  880.  
  881. int64_t noPongTime = timeNow - lastPong;
  882. if ((hasLostConnection || noPongTime >= 7000) && attackedCreature && attackedCreature->getPlayer()) {
  883. setAttackedCreature(nullptr);
  884. }
  885.  
  886. int32_t noPongKickTime = vocation->getNoPongKickTime();
  887. if (pzLocked && noPongKickTime < 60000) {
  888. noPongKickTime = 60000;
  889. }
  890.  
  891. if (noPongTime >= noPongKickTime) {
  892. if (isConnecting || getTile()->hasFlag(TILESTATE_NOLOGOUT)) {
  893. return;
  894. }
  895.  
  896. if (!g_creatureEvents->playerLogout(this)) {
  897. return;
  898. }
  899.  
  900. if (client) {
  901. client->logout(true, true);
  902. } else {
  903. g_game.removeCreature(this, true);
  904. }
  905. }
  906. }
  907.  
  908. Item* Player::getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen)
  909. {
  910. windowTextId = this->windowTextId;
  911. maxWriteLen = this->maxWriteLen;
  912. return writeItem;
  913. }
  914.  
  915. void Player::setWriteItem(Item* item, uint16_t maxWriteLen /*= 0*/)
  916. {
  917. windowTextId++;
  918.  
  919. if (writeItem) {
  920. writeItem->decrementReferenceCounter();
  921. }
  922.  
  923. if (item) {
  924. writeItem = item;
  925. this->maxWriteLen = maxWriteLen;
  926. writeItem->incrementReferenceCounter();
  927. } else {
  928. writeItem = nullptr;
  929. this->maxWriteLen = 0;
  930. }
  931. }
  932.  
  933. House* Player::getEditHouse(uint32_t& windowTextId, uint32_t& listId)
  934. {
  935. windowTextId = this->windowTextId;
  936. listId = this->editListId;
  937. return editHouse;
  938. }
  939.  
  940. void Player::setEditHouse(House* house, uint32_t listId /*= 0*/)
  941. {
  942. windowTextId++;
  943. editHouse = house;
  944. editListId = listId;
  945. }
  946.  
  947. void Player::sendHouseWindow(House* house, uint32_t listId) const
  948. {
  949. if (!client) {
  950. return;
  951. }
  952.  
  953. std::string text;
  954. if (house->getAccessList(listId, text)) {
  955. client->sendHouseWindow(windowTextId, text);
  956. }
  957. }
  958.  
  959. //container
  960. void Player::sendAddContainerItem(const Container* container, const Item* item)
  961. {
  962. if (!client) {
  963. return;
  964. }
  965.  
  966. for (const auto& it : openContainers) {
  967. const OpenContainer& openContainer = it.second;
  968. if (openContainer.container != container) {
  969. continue;
  970. }
  971.  
  972. uint16_t slot = openContainer.index;
  973. /*if (container->getID() == ITEM_BROWSEFIELD) {
  974. uint16_t containerSize = container->size() - 1;
  975. uint16_t pageEnd = openContainer.index + container->capacity() - 1;
  976. if (containerSize > pageEnd) {
  977. slot = pageEnd;
  978. item = container->getItemByIndex(pageEnd);
  979. } else {
  980. slot = containerSize;
  981. }
  982. } else*/ if (openContainer.index >= container->capacity()) {
  983. item = container->getItemByIndex(openContainer.index);
  984. }
  985.  
  986. if (item) {
  987. client->sendAddContainerItem(it.first, slot, item);
  988. }
  989. }
  990. }
  991.  
  992. void Player::sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* newItem)
  993. {
  994. if (!client) {
  995. return;
  996. }
  997.  
  998. for (const auto& it : openContainers) {
  999. const OpenContainer& openContainer = it.second;
  1000. if (openContainer.container != container) {
  1001. continue;
  1002. }
  1003.  
  1004. if (slot < openContainer.index) {
  1005. continue;
  1006. }
  1007.  
  1008. uint16_t pageEnd = openContainer.index + container->capacity();
  1009. if (slot >= pageEnd) {
  1010. continue;
  1011. }
  1012.  
  1013. client->sendUpdateContainerItem(it.first, slot, newItem);
  1014. }
  1015. }
  1016.  
  1017. void Player::sendRemoveContainerItem(const Container* container, uint16_t slot)
  1018. {
  1019. if (!client) {
  1020. return;
  1021. }
  1022.  
  1023. for (auto& it : openContainers) {
  1024. OpenContainer& openContainer = it.second;
  1025. if (openContainer.container != container) {
  1026. continue;
  1027. }
  1028.  
  1029. uint16_t& firstIndex = openContainer.index;
  1030. if (firstIndex > 0 && firstIndex >= container->size() - 1) {
  1031. firstIndex -= container->capacity();
  1032. sendContainer(it.first, container, false, firstIndex);
  1033. }
  1034.  
  1035. client->sendRemoveContainerItem(it.first, std::max<uint16_t>(slot, firstIndex), container->getItemByIndex(container->capacity() + firstIndex));
  1036. }
  1037. }
  1038.  
  1039. void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem,
  1040. const ItemType& oldType, const Item* newItem, const ItemType& newType)
  1041. {
  1042. Creature::onUpdateTileItem(tile, pos, oldItem, oldType, newItem, newType);
  1043.  
  1044. if (oldItem != newItem) {
  1045. onRemoveTileItem(tile, pos, oldType, oldItem);
  1046. }
  1047.  
  1048. if (tradeState != TRADE_TRANSFER) {
  1049. if (tradeItem && oldItem == tradeItem) {
  1050. g_game.internalCloseTrade(this);
  1051. }
  1052. }
  1053. }
  1054.  
  1055. void Player::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType,
  1056. const Item* item)
  1057. {
  1058. Creature::onRemoveTileItem(tile, pos, iType, item);
  1059.  
  1060. if (tradeState != TRADE_TRANSFER) {
  1061. checkTradeState(item);
  1062.  
  1063. if (tradeItem) {
  1064. const Container* container = item->getContainer();
  1065. if (container && container->isHoldingItem(tradeItem)) {
  1066. g_game.internalCloseTrade(this);
  1067. }
  1068. }
  1069. }
  1070. }
  1071.  
  1072. void Player::onCreatureAppear(Creature* creature, bool isLogin)
  1073. {
  1074. Creature::onCreatureAppear(creature, isLogin);
  1075.  
  1076. if (isLogin && creature == this) {
  1077. //sendItems();
  1078.  
  1079. for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
  1080. Item* item = inventory[slot];
  1081. if (item) {
  1082. item->startDecaying();
  1083. g_moveEvents->onPlayerEquip(this, item, static_cast<slots_t>(slot), false);
  1084. }
  1085. }
  1086.  
  1087. for (Condition* condition : storedConditionList) {
  1088. addCondition(condition);
  1089. }
  1090. storedConditionList.clear();
  1091.  
  1092. updateRegeneration();
  1093.  
  1094. BedItem* bed = g_game.getBedBySleeper(guid);
  1095. if (bed) {
  1096. bed->wakeUp(this);
  1097. }
  1098.  
  1099. // load mount speed bonus
  1100. /*uint16_t currentMountId = currentOutfit.lookMount;
  1101. if (currentMountId != 0) {
  1102. Mount* currentMount = g_game.mounts.getMountByClientID(currentMountId);
  1103. if (currentMount && hasMount(currentMount)) {
  1104. g_game.changeSpeed(this, currentMount->speed);
  1105. } else {
  1106. defaultOutfit.lookMount = 0;
  1107. g_game.internalCreatureChangeOutfit(this, defaultOutfit);
  1108. }
  1109. }
  1110.  
  1111. // mounted player moved to pz on login, update mount status
  1112. onChangeZone(getZone());
  1113. */
  1114.  
  1115. if (g_config.getBoolean(ConfigManager::PLAYER_CONSOLE_LOGS)) {
  1116. std::cout << name << " has logged in." << std::endl;
  1117. }
  1118.  
  1119. if (guild) {
  1120. guild->addMember(this);
  1121. }
  1122.  
  1123. int32_t offlineTime;
  1124. if (getLastLogout() != 0) {
  1125. // Not counting more than 21 days to prevent overflow when multiplying with 1000 (for milliseconds).
  1126. offlineTime = std::min<int32_t>(time(nullptr) - getLastLogout(), 86400 * 21);
  1127. } else {
  1128. offlineTime = 0;
  1129. }
  1130.  
  1131. for (Condition* condition : getMuteConditions()) {
  1132. condition->setTicks(condition->getTicks() - (offlineTime * 1000));
  1133. if (condition->getTicks() <= 0) {
  1134. removeCondition(condition);
  1135. }
  1136. }
  1137.  
  1138. g_game.checkPlayersRecord();
  1139. IOLoginData::updateOnlineStatus(guid, true);
  1140. }
  1141. }
  1142.  
  1143. void Player::onAttackedCreatureDisappear(bool isLogout)
  1144. {
  1145. sendCancelTarget();
  1146.  
  1147. if (!isLogout) {
  1148. sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost.");
  1149. }
  1150. }
  1151.  
  1152. void Player::onFollowCreatureDisappear(bool isLogout)
  1153. {
  1154. sendCancelTarget();
  1155.  
  1156. if (!isLogout) {
  1157. sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost.");
  1158. }
  1159. }
  1160.  
  1161. void Player::onChangeZone(ZoneType_t zone)
  1162. {
  1163. if (zone == ZONE_PROTECTION) {
  1164. if (attackedCreature && !hasFlag(PlayerFlag_IgnoreProtectionZone)) {
  1165. setAttackedCreature(nullptr);
  1166. onAttackedCreatureDisappear(false);
  1167. }
  1168.  
  1169. /*if (!group->access && isMounted()) {
  1170. dismount();
  1171. g_game.internalCreatureChangeOutfit(this, defaultOutfit);
  1172. wasMounted = true;
  1173. }*/
  1174. }/*else {
  1175. if (wasMounted) {
  1176. toggleMount(true);
  1177. wasMounted = false;
  1178. }
  1179. }*/
  1180.  
  1181. //g_game.updateCreatureWalkthrough(this);
  1182. sendIcons();
  1183. }
  1184.  
  1185. void Player::onAttackedCreatureChangeZone(ZoneType_t zone)
  1186. {
  1187. if (zone == ZONE_PROTECTION) {
  1188. if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) {
  1189. setAttackedCreature(nullptr);
  1190. onAttackedCreatureDisappear(false);
  1191. }
  1192. } else if (zone == ZONE_NOPVP) {
  1193. if (attackedCreature->getPlayer()) {
  1194. if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) {
  1195. setAttackedCreature(nullptr);
  1196. onAttackedCreatureDisappear(false);
  1197. }
  1198. }
  1199. } else if (zone == ZONE_NORMAL) {
  1200. //attackedCreature can leave a pvp zone if not pzlocked
  1201. if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) {
  1202. if (attackedCreature->getPlayer()) {
  1203. setAttackedCreature(nullptr);
  1204. onAttackedCreatureDisappear(false);
  1205. }
  1206. }
  1207. }
  1208. }
  1209.  
  1210. void Player::onRemoveCreature(Creature* creature, bool isLogout)
  1211. {
  1212. Creature::onRemoveCreature(creature, isLogout);
  1213.  
  1214. if (creature == this) {
  1215. if (isLogout) {
  1216. loginPosition = getPosition();
  1217. }
  1218.  
  1219. lastLogout = time(nullptr);
  1220.  
  1221. if (eventWalk != 0) {
  1222. setFollowCreature(nullptr);
  1223. }
  1224.  
  1225. if (tradePartner) {
  1226. g_game.internalCloseTrade(this);
  1227. }
  1228.  
  1229. //closeShopWindow();
  1230.  
  1231. clearPartyInvitations();
  1232.  
  1233. if (party) {
  1234. party->leaveParty(this);
  1235. }
  1236.  
  1237. g_chat->removeUserFromAllChannels(*this);
  1238.  
  1239. if (g_config.getBoolean(ConfigManager::PLAYER_CONSOLE_LOGS)) {
  1240. std::cout << getName() << " has logged out." << std::endl;
  1241. }
  1242.  
  1243. if (guild) {
  1244. guild->removeMember(this);
  1245. }
  1246.  
  1247. IOLoginData::updateOnlineStatus(guid, false);
  1248.  
  1249. bool saved = false;
  1250. for (uint32_t tries = 0; tries < 3; ++tries) {
  1251. if (IOLoginData::savePlayer(this)) {
  1252. saved = true;
  1253. break;
  1254. }
  1255. }
  1256.  
  1257. if (!saved) {
  1258. std::cout << "Error while saving player: " << getName() << std::endl;
  1259. }
  1260. }
  1261. }
  1262.  
  1263. void Player::openShopWindow(Npc* npc, const std::list<ShopInfo>& shop)
  1264. {
  1265. shopItemList = shop;
  1266. sendShop(npc);
  1267. sendSaleItemList();
  1268. }
  1269.  
  1270. bool Player::closeShopWindow(bool sendCloseShopWindow /*= true*/)
  1271. {
  1272. //unreference callbacks
  1273. int32_t onBuy;
  1274. int32_t onSell;
  1275.  
  1276. Npc* npc = getShopOwner(onBuy, onSell);
  1277. if (!npc) {
  1278. shopItemList.clear();
  1279. return false;
  1280. }
  1281.  
  1282. setShopOwner(nullptr, -1, -1);
  1283. npc->onPlayerEndTrade(this, onBuy, onSell);
  1284.  
  1285. if (sendCloseShopWindow) {
  1286. sendCloseShop();
  1287. }
  1288.  
  1289. shopItemList.clear();
  1290. return true;
  1291. }
  1292.  
  1293. void Player::onWalk(Direction& dir)
  1294. {
  1295. Creature::onWalk(dir);
  1296. setNextActionTask(nullptr);
  1297. //setNextAction(OTSYS_TIME() + getStepDuration(dir));
  1298. }
  1299.  
  1300. void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos,
  1301. const Tile* oldTile, const Position& oldPos, bool teleport)
  1302. {
  1303. Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport);
  1304.  
  1305. if (hasFollowPath && (creature == followCreature || (creature == this && followCreature))) {
  1306. isUpdatingPath = false;
  1307. g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID())));
  1308. }
  1309.  
  1310. if (creature != this) {
  1311. return;
  1312. }
  1313.  
  1314. if (tradeState != TRADE_TRANSFER) {
  1315. //check if we should close trade
  1316. if (tradeItem && !Position::areInRange<1, 1, 0>(tradeItem->getPosition(), getPosition())) {
  1317. g_game.internalCloseTrade(this);
  1318. }
  1319.  
  1320. if (tradePartner && !Position::areInRange<2, 2, 0>(tradePartner->getPosition(), getPosition())) {
  1321. g_game.internalCloseTrade(this);
  1322. }
  1323. }
  1324.  
  1325. // close modal windows
  1326. if (!modalWindows.empty()) {
  1327. // TODO: This shouldn't be hard-coded
  1328. for (uint32_t modalWindowId : modalWindows) {
  1329. if (modalWindowId == std::numeric_limits<uint32_t>::max()) {
  1330. sendTextMessage(MESSAGE_EVENT_ADVANCE, "Offline training aborted.");
  1331. break;
  1332. }
  1333. }
  1334. modalWindows.clear();
  1335. }
  1336.  
  1337. // leave market
  1338. if (inMarket) {
  1339. inMarket = false;
  1340. }
  1341.  
  1342. if (party) {
  1343. party->updateSharedExperience();
  1344. }
  1345.  
  1346. if (teleport || oldPos.z != newPos.z) {
  1347. int32_t ticks = g_config.getNumber(ConfigManager::STAIRHOP_DELAY);
  1348. if (ticks > 0) {
  1349. if (Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_PACIFIED, ticks, 0)) {
  1350. addCondition(condition);
  1351. }
  1352. }
  1353. }
  1354. }
  1355.  
  1356. //container
  1357. void Player::onAddContainerItem(const Item* item)
  1358. {
  1359. checkTradeState(item);
  1360. }
  1361.  
  1362. void Player::onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem)
  1363. {
  1364. if (oldItem != newItem) {
  1365. onRemoveContainerItem(container, oldItem);
  1366. }
  1367.  
  1368. if (tradeState != TRADE_TRANSFER) {
  1369. checkTradeState(oldItem);
  1370. }
  1371. }
  1372.  
  1373. void Player::onRemoveContainerItem(const Container* container, const Item* item)
  1374. {
  1375. if (tradeState != TRADE_TRANSFER) {
  1376. checkTradeState(item);
  1377.  
  1378. if (tradeItem) {
  1379. if (tradeItem->getParent() != container && container->isHoldingItem(tradeItem)) {
  1380. g_game.internalCloseTrade(this);
  1381. }
  1382. }
  1383. }
  1384. }
  1385.  
  1386. void Player::onCloseContainer(const Container* container)
  1387. {
  1388. if (!client) {
  1389. return;
  1390. }
  1391.  
  1392. for (const auto& it : openContainers) {
  1393. if (it.second.container == container) {
  1394. client->sendCloseContainer(it.first);
  1395. }
  1396. }
  1397. }
  1398.  
  1399. void Player::onSendContainer(const Container* container)
  1400. {
  1401. if (!client) {
  1402. return;
  1403. }
  1404.  
  1405. bool hasParent = container->hasParent();
  1406. for (const auto& it : openContainers) {
  1407. const OpenContainer& openContainer = it.second;
  1408. if (openContainer.container == container) {
  1409. client->sendContainer(it.first, container, hasParent, openContainer.index);
  1410. }
  1411. }
  1412. }
  1413.  
  1414. //inventory
  1415. void Player::onUpdateInventoryItem(Item* oldItem, Item* newItem)
  1416. {
  1417. if (oldItem != newItem) {
  1418. onRemoveInventoryItem(oldItem);
  1419. }
  1420.  
  1421. if (tradeState != TRADE_TRANSFER) {
  1422. checkTradeState(oldItem);
  1423. }
  1424. }
  1425.  
  1426. void Player::onRemoveInventoryItem(Item* item)
  1427. {
  1428. if (tradeState != TRADE_TRANSFER) {
  1429. checkTradeState(item);
  1430.  
  1431. if (tradeItem) {
  1432. const Container* container = item->getContainer();
  1433. if (container && container->isHoldingItem(tradeItem)) {
  1434. g_game.internalCloseTrade(this);
  1435. }
  1436. }
  1437. }
  1438. }
  1439.  
  1440. void Player::checkTradeState(const Item* item)
  1441. {
  1442. if (!tradeItem || tradeState == TRADE_TRANSFER) {
  1443. return;
  1444. }
  1445.  
  1446. if (tradeItem == item) {
  1447. g_game.internalCloseTrade(this);
  1448. } else {
  1449. const Container* container = dynamic_cast<const Container*>(item->getParent());
  1450. while (container) {
  1451. if (container == tradeItem) {
  1452. g_game.internalCloseTrade(this);
  1453. break;
  1454. }
  1455.  
  1456. container = dynamic_cast<const Container*>(container->getParent());
  1457. }
  1458. }
  1459. }
  1460.  
  1461. void Player::setNextWalkActionTask(SchedulerTask* task)
  1462. {
  1463. if (walkTaskEvent != 0) {
  1464. g_scheduler.stopEvent(walkTaskEvent);
  1465. walkTaskEvent = 0;
  1466. }
  1467.  
  1468. delete walkTask;
  1469. walkTask = task;
  1470. }
  1471.  
  1472. void Player::setNextWalkTask(SchedulerTask* task)
  1473. {
  1474. if (nextStepEvent != 0) {
  1475. g_scheduler.stopEvent(nextStepEvent);
  1476. nextStepEvent = 0;
  1477. }
  1478.  
  1479. if (task) {
  1480. nextStepEvent = g_scheduler.addEvent(task);
  1481. resetIdleTime();
  1482. }
  1483. }
  1484.  
  1485. void Player::setNextActionTask(SchedulerTask* task, bool resetIdleTime /*= true */)
  1486. {
  1487. if (actionTaskEvent != 0) {
  1488. g_scheduler.stopEvent(actionTaskEvent);
  1489. actionTaskEvent = 0;
  1490. }
  1491.  
  1492. if (task) {
  1493. actionTaskEvent = g_scheduler.addEvent(task);
  1494. if (resetIdleTime) {
  1495. this->resetIdleTime();
  1496. }
  1497. }
  1498. }
  1499.  
  1500. uint32_t Player::getNextActionTime() const
  1501. {
  1502. return std::max<int64_t>(SCHEDULER_MINTICKS, nextAction - OTSYS_TIME());
  1503. }
  1504.  
  1505. void Player::onThink(uint32_t interval)
  1506. {
  1507. Creature::onThink(interval);
  1508.  
  1509. sendPing();
  1510.  
  1511. MessageBufferTicks += interval;
  1512. if (MessageBufferTicks >= 1500) {
  1513. MessageBufferTicks = 0;
  1514. addMessageBuffer();
  1515. }
  1516.  
  1517. if (!getTile()->hasFlag(TILESTATE_NOLOGOUT) && !isAccessPlayer()) {
  1518. idleTime += interval;
  1519. const int32_t kickAfterMinutes = g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES);
  1520. if (idleTime > (kickAfterMinutes * 60000) + 60000) {
  1521. kickPlayer(true);
  1522. } else if (client && idleTime == 60000 * kickAfterMinutes) {
  1523. client->sendTextMessage(TextMessage(MESSAGE_STATUS_WARNING, fmt::format("There was no variation in your behaviour for {:d} minutes. You will be disconnected in one minute if there is no change in your actions until then.", kickAfterMinutes)));
  1524. }
  1525. }
  1526.  
  1527. if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) {
  1528. checkSkullTicks(interval / 1000);
  1529. }
  1530.  
  1531. /*addOfflineTrainingTime(interval);
  1532. if (lastStatsTrainingTime != getOfflineTrainingTime() / 60 / 1000) {
  1533. sendStats();
  1534. }*/
  1535. }
  1536.  
  1537. uint32_t Player::isMuted() const
  1538. {
  1539. if (hasFlag(PlayerFlag_CannotBeMuted)) {
  1540. return 0;
  1541. }
  1542.  
  1543. int32_t muteTicks = 0;
  1544. for (Condition* condition : conditions) {
  1545. if (condition->getType() == CONDITION_MUTED && condition->getTicks() > muteTicks) {
  1546. muteTicks = condition->getTicks();
  1547. }
  1548. }
  1549. return static_cast<uint32_t>(muteTicks) / 1000;
  1550. }
  1551.  
  1552. void Player::addMessageBuffer()
  1553. {
  1554. if (MessageBufferCount > 0 && g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER) != 0 && !hasFlag(PlayerFlag_CannotBeMuted)) {
  1555. --MessageBufferCount;
  1556. }
  1557. }
  1558.  
  1559. void Player::removeMessageBuffer()
  1560. {
  1561. if (hasFlag(PlayerFlag_CannotBeMuted)) {
  1562. return;
  1563. }
  1564.  
  1565. const int32_t maxMessageBuffer = g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER);
  1566. if (maxMessageBuffer != 0 && MessageBufferCount <= maxMessageBuffer + 1) {
  1567. if (++MessageBufferCount > maxMessageBuffer) {
  1568. uint32_t muteCount = 1;
  1569. auto it = muteCountMap.find(guid);
  1570. if (it != muteCountMap.end()) {
  1571. muteCount = it->second;
  1572. }
  1573.  
  1574. uint32_t muteTime = 5 * muteCount * muteCount;
  1575. muteCountMap[guid] = muteCount + 1;
  1576. Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_MUTED, muteTime * 1000, 0);
  1577. addCondition(condition);
  1578.  
  1579. sendTextMessage(MESSAGE_STATUS_SMALL, fmt::format("You are muted for {:d} seconds.", muteTime));
  1580. }
  1581. }
  1582. }
  1583.  
  1584. void Player::drainHealth(Creature* attacker, int32_t damage)
  1585. {
  1586. Creature::drainHealth(attacker, damage);
  1587. sendStats();
  1588. }
  1589.  
  1590. void Player::drainMana(Creature* attacker, int32_t manaLoss)
  1591. {
  1592. onAttacked();
  1593. changeMana(-manaLoss);
  1594.  
  1595. if (attacker) {
  1596. addDamagePoints(attacker, manaLoss);
  1597. }
  1598.  
  1599. sendStats();
  1600. }
  1601.  
  1602. void Player::addManaSpent(uint64_t amount)
  1603. {
  1604. if (hasFlag(PlayerFlag_NotGainMana)) {
  1605. return;
  1606. }
  1607.  
  1608. uint64_t currReqMana = vocation->getReqMana(magLevel);
  1609. uint64_t nextReqMana = vocation->getReqMana(magLevel + 1);
  1610. if (currReqMana >= nextReqMana) {
  1611. //player has reached max magic level
  1612. return;
  1613. }
  1614.  
  1615. g_events->eventPlayerOnGainSkillTries(this, SKILL_MAGLEVEL, amount);
  1616. if (amount == 0) {
  1617. return;
  1618. }
  1619.  
  1620. bool sendUpdateStats = false;
  1621. while ((manaSpent + amount) >= nextReqMana) {
  1622. amount -= nextReqMana - manaSpent;
  1623.  
  1624. magLevel++;
  1625. manaSpent = 0;
  1626.  
  1627. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You advanced to magic level {:d}.", magLevel));
  1628.  
  1629. g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel);
  1630.  
  1631. sendUpdateStats = true;
  1632. currReqMana = nextReqMana;
  1633. nextReqMana = vocation->getReqMana(magLevel + 1);
  1634. if (currReqMana >= nextReqMana) {
  1635. return;
  1636. }
  1637. }
  1638.  
  1639. manaSpent += amount;
  1640.  
  1641. uint8_t oldPercent = magLevelPercent;
  1642. if (nextReqMana > currReqMana) {
  1643. magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana);
  1644. } else {
  1645. magLevelPercent = 0;
  1646. }
  1647.  
  1648. if (oldPercent != magLevelPercent) {
  1649. sendUpdateStats = true;
  1650. }
  1651.  
  1652. if (sendUpdateStats) {
  1653. sendStats();
  1654. }
  1655. }
  1656.  
  1657. void Player::removeManaSpent(uint64_t amount, bool notify/* = false*/)
  1658. {
  1659. if (amount == 0) {
  1660. return;
  1661. }
  1662.  
  1663. uint32_t oldLevel = magLevel;
  1664. uint8_t oldPercent = magLevelPercent;
  1665.  
  1666. while (amount > manaSpent && magLevel > 0) {
  1667. amount -= manaSpent;
  1668. manaSpent = vocation->getReqMana(magLevel);
  1669. magLevel--;
  1670. }
  1671.  
  1672. manaSpent -= amount;
  1673.  
  1674. uint64_t nextReqMana = vocation->getReqMana(magLevel + 1);
  1675. if (nextReqMana > vocation->getReqMana(magLevel)) {
  1676. magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana);
  1677. } else {
  1678. magLevelPercent = 0;
  1679. }
  1680.  
  1681. if (notify) {
  1682. bool sendUpdateStats = false;
  1683. if (oldLevel != magLevel) {
  1684. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You were downgraded to magic level {:d}.", magLevel));
  1685. sendUpdateStats = true;
  1686. }
  1687.  
  1688. if (sendUpdateStats || oldPercent != magLevelPercent) {
  1689. sendStats();
  1690. }
  1691. }
  1692. }
  1693.  
  1694. void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = false*/)
  1695. {
  1696. uint64_t currLevelExp = Player::getExpForLevel(level);
  1697. uint64_t nextLevelExp = Player::getExpForLevel(level + 1);
  1698. uint64_t rawExp = exp;
  1699. if (currLevelExp >= nextLevelExp) {
  1700. //player has reached max level
  1701. levelPercent = 0;
  1702. sendStats();
  1703. return;
  1704. }
  1705.  
  1706. g_events->eventPlayerOnGainExperience(this, source, exp, rawExp);
  1707. if (exp == 0) {
  1708. return;
  1709. }
  1710.  
  1711. experience += exp;
  1712.  
  1713. if (sendText) {
  1714. std::string expString = std::to_string(exp) + (exp != 1 ? " experience points." : " experience point.");
  1715.  
  1716. /*TextMessage message(MESSAGE_EXPERIENCE, "You gained " + expString);
  1717. message.position = position;
  1718. message.primary.value = exp;
  1719. message.primary.color = TEXTCOLOR_WHITE_EXP;
  1720. sendTextMessage(message);*/
  1721.  
  1722. TextMessage message(MESSAGE_EVENT_DEFAULT, "You gained " + expString);
  1723. sendTextMessage(message);
  1724.  
  1725. ColoredText coloredText(std::to_string(exp), position, TEXTCOLOR_WHITE_EXP);
  1726. sendColoredText(coloredText);
  1727.  
  1728. SpectatorVec spectators;
  1729. g_game.map.getSpectators(spectators, position, false, true);
  1730. spectators.erase(this);
  1731. if (!spectators.empty()) {
  1732. //message.type = MESSAGE_EXPERIENCE_OTHERS;
  1733. message.type = MESSAGE_STATUS_SMALL;
  1734. message.text = getName() + " gained " + expString;
  1735. for (Creature* spectator : spectators) {
  1736. spectator->getPlayer()->sendTextMessage(message);
  1737. spectator->getPlayer()->sendColoredText(coloredText);
  1738. }
  1739. }
  1740. }
  1741.  
  1742. uint32_t prevLevel = level;
  1743. while (experience >= nextLevelExp) {
  1744. ++level;
  1745. healthMax += vocation->getHPGain();
  1746. health += vocation->getHPGain();
  1747. manaMax += vocation->getManaGain();
  1748. mana += vocation->getManaGain();
  1749. capacity += vocation->getCapGain();
  1750.  
  1751. currLevelExp = nextLevelExp;
  1752. nextLevelExp = Player::getExpForLevel(level + 1);
  1753. if (currLevelExp >= nextLevelExp) {
  1754. //player has reached max level
  1755. break;
  1756. }
  1757. }
  1758.  
  1759. if (prevLevel != level) {
  1760. health = getMaxHealth();
  1761. mana = getMaxMana();
  1762.  
  1763. updateBaseSpeed();
  1764. setBaseSpeed(getBaseSpeed());
  1765.  
  1766. g_game.changeSpeed(this, 0);
  1767. g_game.addCreatureHealth(this);
  1768.  
  1769. /*const uint32_t protectionLevel = static_cast<uint32_t>(g_config.getNumber(ConfigManager::PROTECTION_LEVEL));
  1770. if (prevLevel < protectionLevel && level >= protectionLevel) {
  1771. g_game.updateCreatureWalkthrough(this);
  1772. }*/
  1773.  
  1774. if (party) {
  1775. party->updateSharedExperience();
  1776. }
  1777.  
  1778. g_creatureEvents->playerAdvance(this, SKILL_LEVEL, prevLevel, level);
  1779.  
  1780. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You advanced from Level {:d} to Level {:d}.", prevLevel, level));
  1781. }
  1782.  
  1783. if (nextLevelExp > currLevelExp) {
  1784. levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp);
  1785. } else {
  1786. levelPercent = 0;
  1787. }
  1788. sendStats();
  1789. }
  1790.  
  1791. void Player::removeExperience(uint64_t exp, bool sendText/* = false*/)
  1792. {
  1793. if (experience == 0 || exp == 0) {
  1794. return;
  1795. }
  1796.  
  1797. g_events->eventPlayerOnLoseExperience(this, exp);
  1798. if (exp == 0) {
  1799. return;
  1800. }
  1801.  
  1802. uint64_t lostExp = experience;
  1803. experience = std::max<int64_t>(0, experience - exp);
  1804.  
  1805. if (sendText) {
  1806. lostExp -= experience;
  1807.  
  1808. std::string expString = std::to_string(lostExp) + (lostExp != 1 ? " experience points." : " experience point.");
  1809.  
  1810. /*TextMessage message(MESSAGE_EXPERIENCE, "You lost " + expString);
  1811. message.position = position;
  1812. message.primary.value = lostExp;
  1813. message.primary.color = TEXTCOLOR_RED;
  1814. sendTextMessage(message);*/
  1815.  
  1816. TextMessage message(MESSAGE_EVENT_DEFAULT, "You lost " + expString);
  1817. sendTextMessage(message);
  1818.  
  1819. ColoredText coloredText(std::to_string(lostExp), position, TEXTCOLOR_RED);
  1820. sendColoredText(ColoredText(std::to_string(lostExp), position, TEXTCOLOR_RED));
  1821.  
  1822. SpectatorVec spectators;
  1823. g_game.map.getSpectators(spectators, position, false, true);
  1824. spectators.erase(this);
  1825. if (!spectators.empty()) {
  1826. //message.type = MESSAGE_EXPERIENCE_OTHERS;
  1827. message.type = MESSAGE_STATUS_SMALL;
  1828. message.text = getName() + " lost " + expString;
  1829. for (Creature* spectator : spectators) {
  1830. spectator->getPlayer()->sendTextMessage(message);
  1831. spectator->getPlayer()->sendColoredText(coloredText);
  1832. }
  1833. }
  1834. }
  1835.  
  1836. uint32_t oldLevel = level;
  1837. uint64_t currLevelExp = Player::getExpForLevel(level);
  1838.  
  1839. while (level > 1 && experience < currLevelExp) {
  1840. --level;
  1841. healthMax = std::max<int32_t>(0, healthMax - vocation->getHPGain());
  1842. manaMax = std::max<int32_t>(0, manaMax - vocation->getManaGain());
  1843. capacity = std::max<int32_t>(0, capacity - vocation->getCapGain());
  1844. currLevelExp = Player::getExpForLevel(level);
  1845. }
  1846.  
  1847. if (oldLevel != level) {
  1848. health = getMaxHealth();
  1849. mana = getMaxMana();
  1850.  
  1851. updateBaseSpeed();
  1852. setBaseSpeed(getBaseSpeed());
  1853.  
  1854. g_game.changeSpeed(this, 0);
  1855. g_game.addCreatureHealth(this);
  1856.  
  1857. /*const uint32_t protectionLevel = static_cast<uint32_t>(g_config.getNumber(ConfigManager::PROTECTION_LEVEL));
  1858. if (oldLevel >= protectionLevel && level < protectionLevel) {
  1859. g_game.updateCreatureWalkthrough(this);
  1860. }*/
  1861.  
  1862. if (party) {
  1863. party->updateSharedExperience();
  1864. }
  1865.  
  1866. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You were downgraded from Level {:d} to Level {:d}.", oldLevel, level));
  1867. }
  1868.  
  1869. uint64_t nextLevelExp = Player::getExpForLevel(level + 1);
  1870. if (nextLevelExp > currLevelExp) {
  1871. levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp);
  1872. } else {
  1873. levelPercent = 0;
  1874. }
  1875. sendStats();
  1876. }
  1877.  
  1878. uint8_t Player::getPercentLevel(uint64_t count, uint64_t nextLevelCount)
  1879. {
  1880. if (nextLevelCount == 0) {
  1881. return 0;
  1882. }
  1883.  
  1884. uint8_t result = (count * 100) / nextLevelCount;
  1885. if (result > 100) {
  1886. return 0;
  1887. }
  1888. return result;
  1889. }
  1890.  
  1891. void Player::onBlockHit()
  1892. {
  1893. if (shieldBlockCount > 0) {
  1894. --shieldBlockCount;
  1895.  
  1896. if (hasShield()) {
  1897. addSkillAdvance(SKILL_SHIELD, 1);
  1898. }
  1899. }
  1900. }
  1901.  
  1902. void Player::onAttackedCreatureBlockHit(BlockType_t blockType)
  1903. {
  1904. lastAttackBlockType = blockType;
  1905.  
  1906. switch (blockType) {
  1907. case BLOCK_NONE: {
  1908. addAttackSkillPoint = true;
  1909. bloodHitCount = 30;
  1910. shieldBlockCount = 30;
  1911. break;
  1912. }
  1913.  
  1914. case BLOCK_DEFENSE:
  1915. case BLOCK_ARMOR: {
  1916. //need to draw blood every 30 hits
  1917. if (bloodHitCount > 0) {
  1918. addAttackSkillPoint = true;
  1919. --bloodHitCount;
  1920. } else {
  1921. addAttackSkillPoint = false;
  1922. }
  1923. break;
  1924. }
  1925.  
  1926. default: {
  1927. addAttackSkillPoint = false;
  1928. break;
  1929. }
  1930. }
  1931. }
  1932.  
  1933. bool Player::hasShield() const
  1934. {
  1935. Item* item = inventory[CONST_SLOT_LEFT];
  1936. if (item && item->getWeaponType() == WEAPON_SHIELD) {
  1937. return true;
  1938. }
  1939.  
  1940. item = inventory[CONST_SLOT_RIGHT];
  1941. if (item && item->getWeaponType() == WEAPON_SHIELD) {
  1942. return true;
  1943. }
  1944. return false;
  1945. }
  1946.  
  1947. BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage,
  1948. bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/, bool ignoreResistances /* = false*/)
  1949. {
  1950. BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field, ignoreResistances);
  1951.  
  1952. if (attacker) {
  1953. sendCreatureSquare(attacker, SQ_COLOR_BLACK);
  1954. }
  1955.  
  1956. if (blockType != BLOCK_NONE) {
  1957. return blockType;
  1958. }
  1959.  
  1960. if (damage <= 0) {
  1961. damage = 0;
  1962. return BLOCK_ARMOR;
  1963. }
  1964.  
  1965. if (!ignoreResistances) {
  1966. for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_AMMO; ++slot) {
  1967. if (!isItemAbilityEnabled(static_cast<slots_t>(slot))) {
  1968. continue;
  1969. }
  1970.  
  1971. Item* item = inventory[slot];
  1972. if (!item) {
  1973. continue;
  1974. }
  1975.  
  1976. const ItemType& it = Item::items[item->getID()];
  1977. if (!it.abilities) {
  1978. if (damage <= 0) {
  1979. damage = 0;
  1980. return BLOCK_ARMOR;
  1981. }
  1982.  
  1983. continue;
  1984. }
  1985.  
  1986. const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)];
  1987. if (absorbPercent != 0) {
  1988. damage -= std::round(damage * (absorbPercent / 100.));
  1989.  
  1990. uint16_t charges = item->getCharges();
  1991. if (charges != 0) {
  1992. g_game.transformItem(item, item->getID(), charges - 1);
  1993. }
  1994. }
  1995.  
  1996. if (field) {
  1997. const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)];
  1998. if (fieldAbsorbPercent != 0) {
  1999. damage -= std::round(damage * (fieldAbsorbPercent / 100.));
  2000.  
  2001. uint16_t charges = item->getCharges();
  2002. if (charges != 0) {
  2003. g_game.transformItem(item, item->getID(), charges - 1);
  2004. }
  2005. }
  2006. }
  2007. }
  2008. }
  2009.  
  2010. if (damage <= 0) {
  2011. damage = 0;
  2012. blockType = BLOCK_ARMOR;
  2013. }
  2014. return blockType;
  2015. }
  2016.  
  2017. uint32_t Player::getIP() const
  2018. {
  2019. if (client) {
  2020. return client->getIP();
  2021. }
  2022.  
  2023. return 0;
  2024. }
  2025.  
  2026. void Player::death(Creature* lastHitCreature)
  2027. {
  2028. loginPosition = town->getTemplePosition();
  2029.  
  2030. if (skillLoss) {
  2031. //uint8_t unfairFightReduction = 100;
  2032. bool lastHitPlayer = Player::lastHitIsPlayer(lastHitCreature);
  2033.  
  2034. /*if (lastHitPlayer) {
  2035. uint32_t sumLevels = 0;
  2036. uint32_t inFightTicks = g_config.getNumber(ConfigManager::PZ_LOCKED);
  2037. for (const auto& it : damageMap) {
  2038. CountBlock_t cb = it.second;
  2039. if ((OTSYS_TIME() - cb.ticks) <= inFightTicks) {
  2040. Player* damageDealer = g_game.getPlayerByID(it.first);
  2041. if (damageDealer) {
  2042. sumLevels += damageDealer->getLevel();
  2043. }
  2044. }
  2045. }
  2046.  
  2047. if (sumLevels > level) {
  2048. double reduce = level / static_cast<double>(sumLevels);
  2049. unfairFightReduction = std::max<uint8_t>(20, std::floor((reduce * 100) + 0.5));
  2050. }
  2051. }*/
  2052.  
  2053. //Magic level loss
  2054. uint64_t sumMana = 0;
  2055. for (uint32_t i = 1; i <= magLevel; ++i) {
  2056. sumMana += vocation->getReqMana(i);
  2057. }
  2058.  
  2059. //double deathLossPercent = getLostPercent() * (unfairFightReduction / 100.);
  2060. double deathLossPercent = getLostPercent();
  2061. removeManaSpent(static_cast<uint64_t>((sumMana + manaSpent) * deathLossPercent), false);
  2062.  
  2063. //Skill loss
  2064. for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill
  2065. uint64_t sumSkillTries = 0;
  2066. for (uint16_t c = MINIMUM_SKILL_LEVEL + 1; c <= skills[i].level; ++c) { //sum up all required tries for all skill levels
  2067. sumSkillTries += vocation->getReqSkillTries(i, c);
  2068. }
  2069.  
  2070. sumSkillTries += skills[i].tries;
  2071.  
  2072. removeSkillTries(static_cast<skills_t>(i), sumSkillTries * deathLossPercent, false);
  2073. }
  2074.  
  2075. //Level loss
  2076. uint64_t expLoss = static_cast<uint64_t>(experience * deathLossPercent);
  2077. g_events->eventPlayerOnLoseExperience(this, expLoss);
  2078.  
  2079. if (expLoss != 0) {
  2080. uint32_t oldLevel = level;
  2081.  
  2082. if (vocation->getId() == VOCATION_NONE || level > 7) {
  2083. experience -= expLoss;
  2084. }
  2085.  
  2086. while (level > 1 && experience < Player::getExpForLevel(level)) {
  2087. --level;
  2088. healthMax = std::max<int32_t>(0, healthMax - vocation->getHPGain());
  2089. manaMax = std::max<int32_t>(0, manaMax - vocation->getManaGain());
  2090. capacity = std::max<int32_t>(0, capacity - vocation->getCapGain());
  2091. }
  2092.  
  2093. if (oldLevel != level) {
  2094. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You were downgraded from Level {:d} to Level {:d}.", oldLevel, level));
  2095. }
  2096.  
  2097. uint64_t currLevelExp = Player::getExpForLevel(level);
  2098. uint64_t nextLevelExp = Player::getExpForLevel(level + 1);
  2099. if (nextLevelExp > currLevelExp) {
  2100. levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp);
  2101. } else {
  2102. levelPercent = 0;
  2103. }
  2104. }
  2105.  
  2106. if (blessings.test(5)) {
  2107. if (lastHitPlayer) {
  2108. blessings.reset(5);
  2109. } else {
  2110. blessings.reset();
  2111. blessings.set(5);
  2112. }
  2113. } else {
  2114. blessings.reset();
  2115. }
  2116.  
  2117. sendStats();
  2118. sendSkills();
  2119. //sendReLoginWindow(unfairFightReduction);
  2120.  
  2121. /*if (getSkull() == SKULL_BLACK) {
  2122. health = 40;
  2123. mana = 0;
  2124. } else {
  2125. health = healthMax;
  2126. mana = manaMax;
  2127. }*/
  2128.  
  2129. health = healthMax;
  2130. mana = manaMax;
  2131.  
  2132. auto it = conditions.begin(), end = conditions.end();
  2133. while (it != end) {
  2134. Condition* condition = *it;
  2135. if (condition->isPersistent()) {
  2136. it = conditions.erase(it);
  2137.  
  2138. condition->endCondition(this);
  2139. onEndCondition(condition->getType());
  2140. delete condition;
  2141. } else {
  2142. ++it;
  2143. }
  2144. }
  2145. } else {
  2146. setSkillLoss(true);
  2147.  
  2148. auto it = conditions.begin(), end = conditions.end();
  2149. while (it != end) {
  2150. Condition* condition = *it;
  2151. if (condition->isPersistent()) {
  2152. it = conditions.erase(it);
  2153.  
  2154. condition->endCondition(this);
  2155. onEndCondition(condition->getType());
  2156. delete condition;
  2157. } else {
  2158. ++it;
  2159. }
  2160. }
  2161.  
  2162. health = healthMax;
  2163. g_game.internalTeleport(this, getTemplePosition(), true);
  2164. g_game.addCreatureHealth(this);
  2165. onThink(EVENT_CREATURE_THINK_INTERVAL);
  2166. onIdleStatus();
  2167. sendStats();
  2168. }
  2169. }
  2170.  
  2171. bool Player::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified)
  2172. {
  2173. if (getZone() != ZONE_PVP || !Player::lastHitIsPlayer(lastHitCreature)) {
  2174. return Creature::dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified);
  2175. }
  2176.  
  2177. setDropLoot(true);
  2178. return false;
  2179. }
  2180.  
  2181. Item* Player::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature)
  2182. {
  2183. Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature);
  2184. if (corpse && corpse->getContainer()) {
  2185. std::unordered_map<std::string, uint16_t> names;
  2186. for (const auto& killer : getKillers()) {
  2187. ++names[killer->getName()];
  2188. }
  2189.  
  2190. if (lastHitCreature) {
  2191. if (!mostDamageCreature) {
  2192. corpse->setSpecialDescription(fmt::format("You recognize {:s}. {:s} was killed by {:s}{:s}", getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", lastHitCreature->getNameDescription(), names.size() > 1 ? " and others." : "."));
  2193. } else if (lastHitCreature != mostDamageCreature && names[lastHitCreature->getName()] == 1) {
  2194. corpse->setSpecialDescription(fmt::format("You recognize {:s}. {:s} was killed by {:s}, {:s}{:s}", getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", mostDamageCreature->getNameDescription(), lastHitCreature->getNameDescription(), names.size() > 2 ? " and others." : "."));
  2195. } else {
  2196. corpse->setSpecialDescription(fmt::format("You recognize {:s}. {:s} was killed by {:s} and others.", getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", mostDamageCreature->getNameDescription()));
  2197. }
  2198. } else if (mostDamageCreature) {
  2199. if (names.size() > 1) {
  2200. corpse->setSpecialDescription(fmt::format("You recognize {:s}. {:s} was killed by something evil, {:s}, and others", getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", mostDamageCreature->getNameDescription()));
  2201. } else {
  2202. corpse->setSpecialDescription(fmt::format("You recognize {:s}. {:s} was killed by something evil and others", getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", mostDamageCreature->getNameDescription()));
  2203. }
  2204. } else {
  2205. corpse->setSpecialDescription(fmt::format("You recognize {:s}. {:s} was killed by something evil {:s}", getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", names.size() ? " and others." : "."));
  2206. }
  2207. }
  2208. return corpse;
  2209. }
  2210.  
  2211. void Player::addInFightTicks(bool pzlock /*= false*/)
  2212. {
  2213. if (hasFlag(PlayerFlag_NotGainInFight)) {
  2214. return;
  2215. }
  2216.  
  2217. if (pzlock) {
  2218. pzLocked = true;
  2219. }
  2220.  
  2221. Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::PZ_LOCKED), 0);
  2222. addCondition(condition);
  2223. }
  2224.  
  2225. void Player::removeList()
  2226. {
  2227. g_game.removePlayer(this);
  2228.  
  2229. for (const auto& it : g_game.getPlayers()) {
  2230. it.second->notifyStatusChange(this, VIPSTATUS_OFFLINE);
  2231. }
  2232. }
  2233.  
  2234. void Player::addList()
  2235. {
  2236. for (const auto& it : g_game.getPlayers()) {
  2237. it.second->notifyStatusChange(this, VIPSTATUS_ONLINE);
  2238. }
  2239.  
  2240. g_game.addPlayer(this);
  2241. }
  2242.  
  2243. void Player::kickPlayer(bool displayEffect)
  2244. {
  2245. g_creatureEvents->playerLogout(this);
  2246. if (client) {
  2247. client->logout(displayEffect, true);
  2248. } else {
  2249. g_game.removeCreature(this);
  2250. }
  2251. }
  2252.  
  2253. void Player::notifyStatusChange(Player* loginPlayer, VipStatus_t status)
  2254. {
  2255. if (!client) {
  2256. return;
  2257. }
  2258.  
  2259. auto it = VIPList.find(loginPlayer->guid);
  2260. if (it == VIPList.end()) {
  2261. return;
  2262. }
  2263.  
  2264. client->sendUpdatedVIPStatus(loginPlayer->guid, status);
  2265.  
  2266. if (status == VIPSTATUS_ONLINE) {
  2267. client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged in."));
  2268. } else if (status == VIPSTATUS_OFFLINE) {
  2269. client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged out."));
  2270. }
  2271. }
  2272.  
  2273. bool Player::removeVIP(uint32_t vipGuid)
  2274. {
  2275. if (VIPList.erase(vipGuid) == 0) {
  2276. return false;
  2277. }
  2278.  
  2279. IOLoginData::removeVIPEntry(accountNumber, vipGuid);
  2280. return true;
  2281. }
  2282.  
  2283. bool Player::addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status)
  2284. {
  2285. if (VIPList.size() >= getMaxVIPEntries()) {
  2286. sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add more buddies.");
  2287. return false;
  2288. }
  2289.  
  2290. auto result = VIPList.insert(vipGuid);
  2291. if (!result.second) {
  2292. sendTextMessage(MESSAGE_STATUS_SMALL, "This player is already in your list.");
  2293. return false;
  2294. }
  2295.  
  2296. IOLoginData::addVIPEntry(accountNumber, vipGuid, "", 0, false);
  2297. if (client) {
  2298. client->sendVIP(vipGuid, vipName, "", 0, false, status);
  2299. }
  2300. return true;
  2301. }
  2302.  
  2303. bool Player::addVIPInternal(uint32_t vipGuid)
  2304. {
  2305. if (VIPList.size() >= getMaxVIPEntries()) {
  2306. return false;
  2307. }
  2308.  
  2309. return VIPList.insert(vipGuid).second;
  2310. }
  2311.  
  2312. bool Player::editVIP(uint32_t vipGuid, const std::string& description, uint32_t icon, bool notify)
  2313. {
  2314. auto it = VIPList.find(vipGuid);
  2315. if (it == VIPList.end()) {
  2316. return false; // player is not in VIP
  2317. }
  2318.  
  2319. IOLoginData::editVIPEntry(accountNumber, vipGuid, description, icon, notify);
  2320. return true;
  2321. }
  2322.  
  2323. //close container and its child containers
  2324. void Player::autoCloseContainers(const Container* container)
  2325. {
  2326. std::vector<uint32_t> closeList;
  2327. for (const auto& it : openContainers) {
  2328. Container* tmpContainer = it.second.container;
  2329. while (tmpContainer) {
  2330. if (tmpContainer->isRemoved() || tmpContainer == container) {
  2331. closeList.push_back(it.first);
  2332. break;
  2333. }
  2334.  
  2335. tmpContainer = dynamic_cast<Container*>(tmpContainer->getParent());
  2336. }
  2337. }
  2338.  
  2339. for (uint32_t containerId : closeList) {
  2340. closeContainer(containerId);
  2341. if (client) {
  2342. client->sendCloseContainer(containerId);
  2343. }
  2344. }
  2345. }
  2346.  
  2347. bool Player::hasCapacity(const Item* item, uint32_t count) const
  2348. {
  2349. if (hasFlag(PlayerFlag_CannotPickupItem)) {
  2350. return false;
  2351. }
  2352.  
  2353. if (hasFlag(PlayerFlag_HasInfiniteCapacity) || item->getTopParent() == this) {
  2354. return true;
  2355. }
  2356.  
  2357. uint32_t itemWeight = item->getContainer() != nullptr ? item->getWeight() : item->getBaseWeight();
  2358. if (item->isStackable()) {
  2359. itemWeight *= count;
  2360. }
  2361. return itemWeight <= getFreeCapacity();
  2362. }
  2363.  
  2364. ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature*) const
  2365. {
  2366. const Item* item = thing.getItem();
  2367. if (item == nullptr) {
  2368. return RETURNVALUE_NOTPOSSIBLE;
  2369. }
  2370.  
  2371. bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags);
  2372. if (childIsOwner) {
  2373. //a child container is querying the player, just check if enough capacity
  2374. bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags);
  2375. if (skipLimit || hasCapacity(item, count)) {
  2376. return RETURNVALUE_NOERROR;
  2377. }
  2378. return RETURNVALUE_NOTENOUGHCAPACITY;
  2379. }
  2380.  
  2381. if (!item->isPickupable()) {
  2382. return RETURNVALUE_CANNOTPICKUP;
  2383. }
  2384.  
  2385. if (item->isStoreItem()) {
  2386. return RETURNVALUE_ITEMCANNOTBEMOVEDTHERE;
  2387. }
  2388.  
  2389. ReturnValue ret = RETURNVALUE_NOERROR;
  2390.  
  2391. const int32_t& slotPosition = item->getSlotPosition();
  2392. if ((slotPosition & SLOTP_HEAD) || (slotPosition & SLOTP_NECKLACE) ||
  2393. (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) ||
  2394. (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) ||
  2395. (slotPosition & SLOTP_RING)) {
  2396. ret = RETURNVALUE_CANNOTBEDRESSED;
  2397. } else if (slotPosition & SLOTP_TWO_HAND) {
  2398. ret = RETURNVALUE_PUTTHISOBJECTINBOTHHANDS;
  2399. } else if ((slotPosition & SLOTP_RIGHT) || (slotPosition & SLOTP_LEFT)) {
  2400. if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) {
  2401. ret = RETURNVALUE_CANNOTBEDRESSED;
  2402. } else {
  2403. ret = RETURNVALUE_PUTTHISOBJECTINYOURHAND;
  2404. }
  2405. }
  2406.  
  2407. switch (index) {
  2408. case CONST_SLOT_HEAD: {
  2409. if (slotPosition & SLOTP_HEAD) {
  2410. ret = RETURNVALUE_NOERROR;
  2411. }
  2412. break;
  2413. }
  2414.  
  2415. case CONST_SLOT_NECKLACE: {
  2416. if (slotPosition & SLOTP_NECKLACE) {
  2417. ret = RETURNVALUE_NOERROR;
  2418. }
  2419. break;
  2420. }
  2421.  
  2422. case CONST_SLOT_BACKPACK: {
  2423. if (slotPosition & SLOTP_BACKPACK) {
  2424. ret = RETURNVALUE_NOERROR;
  2425. }
  2426. break;
  2427. }
  2428.  
  2429. case CONST_SLOT_ARMOR: {
  2430. if (slotPosition & SLOTP_ARMOR) {
  2431. ret = RETURNVALUE_NOERROR;
  2432. }
  2433. break;
  2434. }
  2435.  
  2436. case CONST_SLOT_RIGHT: {
  2437. if (slotPosition & SLOTP_RIGHT) {
  2438. if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) {
  2439. if (item->getWeaponType() != WEAPON_SHIELD) {
  2440. ret = RETURNVALUE_CANNOTBEDRESSED;
  2441. } else {
  2442. const Item* leftItem = inventory[CONST_SLOT_LEFT];
  2443. if (leftItem) {
  2444. if ((leftItem->getSlotPosition() | slotPosition) & SLOTP_TWO_HAND) {
  2445. ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE;
  2446. } else {
  2447. ret = RETURNVALUE_NOERROR;
  2448. }
  2449. } else {
  2450. ret = RETURNVALUE_NOERROR;
  2451. }
  2452. }
  2453. } else if (slotPosition & SLOTP_TWO_HAND) {
  2454. if (inventory[CONST_SLOT_LEFT] && inventory[CONST_SLOT_LEFT] != item) {
  2455. ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE;
  2456. } else {
  2457. ret = RETURNVALUE_NOERROR;
  2458. }
  2459. } else if (inventory[CONST_SLOT_LEFT]) {
  2460. const Item* leftItem = inventory[CONST_SLOT_LEFT];
  2461. WeaponType_t type = item->getWeaponType(), leftType = leftItem->getWeaponType();
  2462.  
  2463. if (leftItem->getSlotPosition() & SLOTP_TWO_HAND) {
  2464. ret = RETURNVALUE_DROPTWOHANDEDITEM;
  2465. } else if (item == leftItem && count == item->getItemCount()) {
  2466. ret = RETURNVALUE_NOERROR;
  2467. } else if (leftType == WEAPON_SHIELD && type == WEAPON_SHIELD) {
  2468. ret = RETURNVALUE_CANONLYUSEONESHIELD;
  2469. } else if (leftType == WEAPON_NONE || type == WEAPON_NONE ||
  2470. leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO
  2471. || type == WEAPON_SHIELD || type == WEAPON_AMMO) {
  2472. ret = RETURNVALUE_NOERROR;
  2473. } else {
  2474. ret = RETURNVALUE_CANONLYUSEONEWEAPON;
  2475. }
  2476. } else {
  2477. ret = RETURNVALUE_NOERROR;
  2478. }
  2479. }
  2480. break;
  2481. }
  2482.  
  2483. case CONST_SLOT_LEFT: {
  2484. if (slotPosition & SLOTP_LEFT) {
  2485. if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) {
  2486. WeaponType_t type = item->getWeaponType();
  2487. if (type == WEAPON_NONE || type == WEAPON_SHIELD) {
  2488. ret = RETURNVALUE_CANNOTBEDRESSED;
  2489. } else if (inventory[CONST_SLOT_RIGHT] && (slotPosition & SLOTP_TWO_HAND)) {
  2490. ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE;
  2491. } else {
  2492. ret = RETURNVALUE_NOERROR;
  2493. }
  2494. } else if (slotPosition & SLOTP_TWO_HAND) {
  2495. if (inventory[CONST_SLOT_RIGHT] && inventory[CONST_SLOT_RIGHT] != item) {
  2496. ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE;
  2497. } else {
  2498. ret = RETURNVALUE_NOERROR;
  2499. }
  2500. } else if (inventory[CONST_SLOT_RIGHT]) {
  2501. const Item* rightItem = inventory[CONST_SLOT_RIGHT];
  2502. WeaponType_t type = item->getWeaponType(), rightType = rightItem->getWeaponType();
  2503.  
  2504. if (rightItem->getSlotPosition() & SLOTP_TWO_HAND) {
  2505. ret = RETURNVALUE_DROPTWOHANDEDITEM;
  2506. } else if (item == rightItem && count == item->getItemCount()) {
  2507. ret = RETURNVALUE_NOERROR;
  2508. } else if (rightType == WEAPON_SHIELD && type == WEAPON_SHIELD) {
  2509. ret = RETURNVALUE_CANONLYUSEONESHIELD;
  2510. } else if (rightType == WEAPON_NONE || type == WEAPON_NONE ||
  2511. rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO
  2512. || type == WEAPON_SHIELD || type == WEAPON_AMMO) {
  2513. ret = RETURNVALUE_NOERROR;
  2514. } else {
  2515. ret = RETURNVALUE_CANONLYUSEONEWEAPON;
  2516. }
  2517. } else {
  2518. ret = RETURNVALUE_NOERROR;
  2519. }
  2520. }
  2521. break;
  2522. }
  2523.  
  2524. case CONST_SLOT_LEGS: {
  2525. if (slotPosition & SLOTP_LEGS) {
  2526. ret = RETURNVALUE_NOERROR;
  2527. }
  2528. break;
  2529. }
  2530.  
  2531. case CONST_SLOT_FEET: {
  2532. if (slotPosition & SLOTP_FEET) {
  2533. ret = RETURNVALUE_NOERROR;
  2534. }
  2535. break;
  2536. }
  2537.  
  2538. case CONST_SLOT_RING: {
  2539. if (slotPosition & SLOTP_RING) {
  2540. ret = RETURNVALUE_NOERROR;
  2541. }
  2542. break;
  2543. }
  2544.  
  2545. case CONST_SLOT_AMMO: {
  2546. if ((slotPosition & SLOTP_AMMO) || g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) {
  2547. ret = RETURNVALUE_NOERROR;
  2548. }
  2549. break;
  2550. }
  2551.  
  2552. case CONST_SLOT_WHEREEVER:
  2553. case -1:
  2554. ret = RETURNVALUE_NOTENOUGHROOM;
  2555. break;
  2556.  
  2557. default:
  2558. ret = RETURNVALUE_NOTPOSSIBLE;
  2559. break;
  2560. }
  2561.  
  2562. if (ret != RETURNVALUE_NOERROR && ret != RETURNVALUE_NOTENOUGHROOM) {
  2563. return ret;
  2564. }
  2565.  
  2566. //check if enough capacity
  2567. if (!hasCapacity(item, count)) {
  2568. return RETURNVALUE_NOTENOUGHCAPACITY;
  2569. }
  2570.  
  2571. ret = g_moveEvents->onPlayerEquip(const_cast<Player*>(this), const_cast<Item*>(item), static_cast<slots_t>(index), true);
  2572. if (ret != RETURNVALUE_NOERROR) {
  2573. return ret;
  2574. }
  2575.  
  2576. //need an exchange with source? (destination item is swapped with currently moved item)
  2577. const Item* inventoryItem = getInventoryItem(static_cast<slots_t>(index));
  2578. if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->getID() != item->getID())) {
  2579. const Cylinder* cylinder = item->getTopParent();
  2580. if (cylinder && (dynamic_cast<const DepotChest*>(cylinder) || dynamic_cast<const Player*>(cylinder))) {
  2581. return RETURNVALUE_NEEDEXCHANGE;
  2582. }
  2583.  
  2584. return RETURNVALUE_NOTENOUGHROOM;
  2585. }
  2586. return ret;
  2587. }
  2588.  
  2589. ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount,
  2590. uint32_t flags) const
  2591. {
  2592. const Item* item = thing.getItem();
  2593. if (item == nullptr) {
  2594. maxQueryCount = 0;
  2595. return RETURNVALUE_NOTPOSSIBLE;
  2596. }
  2597.  
  2598. if (index == INDEX_WHEREEVER) {
  2599. uint32_t n = 0;
  2600. for (int32_t slotIndex = CONST_SLOT_FIRST; slotIndex <= CONST_SLOT_LAST; ++slotIndex) {
  2601. Item* inventoryItem = inventory[slotIndex];
  2602. if (inventoryItem) {
  2603. if (Container* subContainer = inventoryItem->getContainer()) {
  2604. uint32_t queryCount = 0;
  2605. subContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags);
  2606. n += queryCount;
  2607.  
  2608. //iterate through all items, including sub-containers (deep search)
  2609. for (ContainerIterator it = subContainer->iterator(); it.hasNext(); it.advance()) {
  2610. if (Container* tmpContainer = (*it)->getContainer()) {
  2611. queryCount = 0;
  2612. tmpContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags);
  2613. n += queryCount;
  2614. }
  2615. }
  2616. } else if (inventoryItem->isStackable() && item->equals(inventoryItem) && inventoryItem->getItemCount() < 100) {
  2617. uint32_t remainder = (100 - inventoryItem->getItemCount());
  2618.  
  2619. if (queryAdd(slotIndex, *item, remainder, flags) == RETURNVALUE_NOERROR) {
  2620. n += remainder;
  2621. }
  2622. }
  2623. } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot
  2624. if (item->isStackable()) {
  2625. n += 100;
  2626. } else {
  2627. ++n;
  2628. }
  2629. }
  2630. }
  2631.  
  2632. maxQueryCount = n;
  2633. } else {
  2634. const Item* destItem = nullptr;
  2635.  
  2636. const Thing* destThing = getThing(index);
  2637. if (destThing) {
  2638. destItem = destThing->getItem();
  2639. }
  2640.  
  2641. if (destItem) {
  2642. if (destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) {
  2643. maxQueryCount = 100 - destItem->getItemCount();
  2644. } else {
  2645. maxQueryCount = 0;
  2646. }
  2647. } else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { //empty slot
  2648. if (item->isStackable()) {
  2649. maxQueryCount = 100;
  2650. } else {
  2651. maxQueryCount = 1;
  2652. }
  2653.  
  2654. return RETURNVALUE_NOERROR;
  2655. }
  2656. }
  2657.  
  2658. if (maxQueryCount < count) {
  2659. return RETURNVALUE_NOTENOUGHROOM;
  2660. }
  2661. return RETURNVALUE_NOERROR;
  2662. }
  2663.  
  2664. ReturnValue Player::queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* /*= nullptr*/) const
  2665. {
  2666. int32_t index = getThingIndex(&thing);
  2667. if (index == -1) {
  2668. return RETURNVALUE_NOTPOSSIBLE;
  2669. }
  2670.  
  2671. const Item* item = thing.getItem();
  2672. if (item == nullptr) {
  2673. return RETURNVALUE_NOTPOSSIBLE;
  2674. }
  2675.  
  2676. if (count == 0 || (item->isStackable() && count > item->getItemCount())) {
  2677. return RETURNVALUE_NOTPOSSIBLE;
  2678. }
  2679.  
  2680. if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) {
  2681. return RETURNVALUE_NOTMOVEABLE;
  2682. }
  2683.  
  2684. return RETURNVALUE_NOERROR;
  2685. }
  2686.  
  2687. Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** destItem,
  2688. uint32_t& flags)
  2689. {
  2690. if (index == 0 /*drop to capacity window*/ || index == INDEX_WHEREEVER) {
  2691. *destItem = nullptr;
  2692.  
  2693. const Item* item = thing.getItem();
  2694. if (item == nullptr) {
  2695. return this;
  2696. }
  2697.  
  2698. bool autoStack = !((flags & FLAG_IGNOREAUTOSTACK) == FLAG_IGNOREAUTOSTACK);
  2699. bool isStackable = item->isStackable();
  2700.  
  2701. std::vector<Container*> containers;
  2702.  
  2703. for (uint32_t slotIndex = CONST_SLOT_FIRST; slotIndex <= CONST_SLOT_LAST; ++slotIndex) {
  2704. Item* inventoryItem = inventory[slotIndex];
  2705. if (inventoryItem) {
  2706. if (inventoryItem == tradeItem) {
  2707. continue;
  2708. }
  2709.  
  2710. if (inventoryItem == item) {
  2711. continue;
  2712. }
  2713.  
  2714. if (autoStack && isStackable) {
  2715. //try find an already existing item to stack with
  2716. if (queryAdd(slotIndex, *item, item->getItemCount(), 0) == RETURNVALUE_NOERROR) {
  2717. if (inventoryItem->equals(item) && inventoryItem->getItemCount() < 100) {
  2718. index = slotIndex;
  2719. *destItem = inventoryItem;
  2720. return this;
  2721. }
  2722. }
  2723.  
  2724. if (Container* subContainer = inventoryItem->getContainer()) {
  2725. containers.push_back(subContainer);
  2726. }
  2727. } else if (Container* subContainer = inventoryItem->getContainer()) {
  2728. containers.push_back(subContainer);
  2729. }
  2730. } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot
  2731. index = slotIndex;
  2732. *destItem = nullptr;
  2733. return this;
  2734. }
  2735. }
  2736.  
  2737. size_t i = 0;
  2738. while (i < containers.size()) {
  2739. Container* tmpContainer = containers[i++];
  2740. if (!autoStack || !isStackable) {
  2741. //we need to find first empty container as fast as we can for non-stackable items
  2742. uint32_t n = tmpContainer->capacity() - std::min(tmpContainer->capacity(), static_cast<uint32_t>(tmpContainer->size()));
  2743. while (n) {
  2744. if (tmpContainer->queryAdd(tmpContainer->capacity() - n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) {
  2745. index = tmpContainer->capacity() - n;
  2746. *destItem = nullptr;
  2747. return tmpContainer;
  2748. }
  2749.  
  2750. --n;
  2751. }
  2752.  
  2753. for (Item* tmpContainerItem : tmpContainer->getItemList()) {
  2754. if (Container* subContainer = tmpContainerItem->getContainer()) {
  2755. containers.push_back(subContainer);
  2756. }
  2757. }
  2758.  
  2759. continue;
  2760. }
  2761.  
  2762. uint32_t n = 0;
  2763.  
  2764. for (Item* tmpItem : tmpContainer->getItemList()) {
  2765. if (tmpItem == tradeItem) {
  2766. continue;
  2767. }
  2768.  
  2769. if (tmpItem == item) {
  2770. continue;
  2771. }
  2772.  
  2773. //try find an already existing item to stack with
  2774. if (tmpItem->equals(item) && tmpItem->getItemCount() < 100) {
  2775. index = n;
  2776. *destItem = tmpItem;
  2777. return tmpContainer;
  2778. }
  2779.  
  2780. if (Container* subContainer = tmpItem->getContainer()) {
  2781. containers.push_back(subContainer);
  2782. }
  2783.  
  2784. n++;
  2785. }
  2786.  
  2787. if (n < tmpContainer->capacity() && tmpContainer->queryAdd(n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) {
  2788. index = n;
  2789. *destItem = nullptr;
  2790. return tmpContainer;
  2791. }
  2792. }
  2793.  
  2794. return this;
  2795. }
  2796.  
  2797. Thing* destThing = getThing(index);
  2798. if (destThing) {
  2799. *destItem = destThing->getItem();
  2800. }
  2801.  
  2802. Cylinder* subCylinder = dynamic_cast<Cylinder*>(destThing);
  2803. if (subCylinder) {
  2804. index = INDEX_WHEREEVER;
  2805. *destItem = nullptr;
  2806. return subCylinder;
  2807. }
  2808. return this;
  2809. }
  2810.  
  2811. void Player::addThing(int32_t index, Thing* thing)
  2812. {
  2813. if (index < CONST_SLOT_FIRST || index > CONST_SLOT_LAST) {
  2814. return /*RETURNVALUE_NOTPOSSIBLE*/;
  2815. }
  2816.  
  2817. Item* item = thing->getItem();
  2818. if (!item) {
  2819. return /*RETURNVALUE_NOTPOSSIBLE*/;
  2820. }
  2821.  
  2822. item->setParent(this);
  2823. inventory[index] = item;
  2824.  
  2825. //send to client
  2826. sendInventoryItem(static_cast<slots_t>(index), item);
  2827. }
  2828.  
  2829. void Player::updateThing(Thing* thing, uint16_t itemId, uint32_t count)
  2830. {
  2831. int32_t index = getThingIndex(thing);
  2832. if (index == -1) {
  2833. return /*RETURNVALUE_NOTPOSSIBLE*/;
  2834. }
  2835.  
  2836. Item* item = thing->getItem();
  2837. if (!item) {
  2838. return /*RETURNVALUE_NOTPOSSIBLE*/;
  2839. }
  2840.  
  2841. item->setID(itemId);
  2842. item->setSubType(count);
  2843.  
  2844. //send to client
  2845. sendInventoryItem(static_cast<slots_t>(index), item);
  2846.  
  2847. //event methods
  2848. onUpdateInventoryItem(item, item);
  2849. }
  2850.  
  2851. void Player::replaceThing(uint32_t index, Thing* thing)
  2852. {
  2853. if (index > CONST_SLOT_LAST) {
  2854. return /*RETURNVALUE_NOTPOSSIBLE*/;
  2855. }
  2856.  
  2857. Item* oldItem = getInventoryItem(static_cast<slots_t>(index));
  2858. if (!oldItem) {
  2859. return /*RETURNVALUE_NOTPOSSIBLE*/;
  2860. }
  2861.  
  2862. Item* item = thing->getItem();
  2863. if (!item) {
  2864. return /*RETURNVALUE_NOTPOSSIBLE*/;
  2865. }
  2866.  
  2867. //send to client
  2868. sendInventoryItem(static_cast<slots_t>(index), item);
  2869.  
  2870. //event methods
  2871. onUpdateInventoryItem(oldItem, item);
  2872.  
  2873. item->setParent(this);
  2874.  
  2875. inventory[index] = item;
  2876. }
  2877.  
  2878. void Player::removeThing(Thing* thing, uint32_t count)
  2879. {
  2880. Item* item = thing->getItem();
  2881. if (!item) {
  2882. return /*RETURNVALUE_NOTPOSSIBLE*/;
  2883. }
  2884.  
  2885. int32_t index = getThingIndex(thing);
  2886. if (index == -1) {
  2887. return /*RETURNVALUE_NOTPOSSIBLE*/;
  2888. }
  2889.  
  2890. if (item->isStackable()) {
  2891. if (count == item->getItemCount()) {
  2892. //send change to client
  2893. sendInventoryItem(static_cast<slots_t>(index), nullptr);
  2894.  
  2895. //event methods
  2896. onRemoveInventoryItem(item);
  2897.  
  2898. item->setParent(nullptr);
  2899. inventory[index] = nullptr;
  2900. } else {
  2901. uint8_t newCount = static_cast<uint8_t>(std::max<int32_t>(0, item->getItemCount() - count));
  2902. item->setItemCount(newCount);
  2903.  
  2904. //send change to client
  2905. sendInventoryItem(static_cast<slots_t>(index), item);
  2906.  
  2907. //event methods
  2908. onUpdateInventoryItem(item, item);
  2909. }
  2910. } else {
  2911. //send change to client
  2912. sendInventoryItem(static_cast<slots_t>(index), nullptr);
  2913.  
  2914. //event methods
  2915. onRemoveInventoryItem(item);
  2916.  
  2917. item->setParent(nullptr);
  2918. inventory[index] = nullptr;
  2919. }
  2920. }
  2921.  
  2922. int32_t Player::getThingIndex(const Thing* thing) const
  2923. {
  2924. for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
  2925. if (inventory[i] == thing) {
  2926. return i;
  2927. }
  2928. }
  2929. return -1;
  2930. }
  2931.  
  2932. size_t Player::getFirstIndex() const
  2933. {
  2934. return CONST_SLOT_FIRST;
  2935. }
  2936.  
  2937. size_t Player::getLastIndex() const
  2938. {
  2939. return CONST_SLOT_LAST + 1;
  2940. }
  2941.  
  2942. uint32_t Player::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const
  2943. {
  2944. uint32_t count = 0;
  2945. for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) {
  2946. Item* item = inventory[i];
  2947. if (!item) {
  2948. continue;
  2949. }
  2950.  
  2951. if (item->getID() == itemId) {
  2952. count += Item::countByType(item, subType);
  2953. }
  2954.  
  2955. if (Container* container = item->getContainer()) {
  2956. for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) {
  2957. if ((*it)->getID() == itemId) {
  2958. count += Item::countByType(*it, subType);
  2959. }
  2960. }
  2961. }
  2962. }
  2963. return count;
  2964. }
  2965.  
  2966. bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped/* = false*/) const
  2967. {
  2968. if (amount == 0) {
  2969. return true;
  2970. }
  2971.  
  2972. std::vector<Item*> itemList;
  2973.  
  2974. uint32_t count = 0;
  2975. for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) {
  2976. Item* item = inventory[i];
  2977. if (!item) {
  2978. continue;
  2979. }
  2980.  
  2981. if (!ignoreEquipped && item->getID() == itemId) {
  2982. uint32_t itemCount = Item::countByType(item, subType);
  2983. if (itemCount == 0) {
  2984. continue;
  2985. }
  2986.  
  2987. itemList.push_back(item);
  2988.  
  2989. count += itemCount;
  2990. if (count >= amount) {
  2991. g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable);
  2992. return true;
  2993. }
  2994. } else if (Container* container = item->getContainer()) {
  2995. for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) {
  2996. Item* containerItem = *it;
  2997. if (containerItem->getID() == itemId) {
  2998. uint32_t itemCount = Item::countByType(containerItem, subType);
  2999. if (itemCount == 0) {
  3000. continue;
  3001. }
  3002.  
  3003. itemList.push_back(containerItem);
  3004.  
  3005. count += itemCount;
  3006. if (count >= amount) {
  3007. g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable);
  3008. return true;
  3009. }
  3010. }
  3011. }
  3012. }
  3013. }
  3014. return false;
  3015. }
  3016.  
  3017. std::map<uint32_t, uint32_t>& Player::getAllItemTypeCount(std::map<uint32_t, uint32_t>& countMap) const
  3018. {
  3019. for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) {
  3020. Item* item = inventory[i];
  3021. if (!item) {
  3022. continue;
  3023. }
  3024.  
  3025. countMap[item->getID()] += Item::countByType(item, -1);
  3026.  
  3027. if (Container* container = item->getContainer()) {
  3028. for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) {
  3029. countMap[(*it)->getID()] += Item::countByType(*it, -1);
  3030. }
  3031. }
  3032. }
  3033. return countMap;
  3034. }
  3035.  
  3036. Thing* Player::getThing(size_t index) const
  3037. {
  3038. if (index >= CONST_SLOT_FIRST && index <= CONST_SLOT_LAST) {
  3039. return inventory[index];
  3040. }
  3041. return nullptr;
  3042. }
  3043.  
  3044. void Player::postAddNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/)
  3045. {
  3046. if (link == LINK_OWNER) {
  3047. //calling movement scripts
  3048. g_moveEvents->onPlayerEquip(this, thing->getItem(), static_cast<slots_t>(index), false);
  3049. }
  3050.  
  3051. //bool requireListUpdate = false;
  3052.  
  3053. if (link == LINK_OWNER || link == LINK_TOPPARENT) {
  3054. //const Item* i = (oldParent ? oldParent->getItem() : nullptr);
  3055.  
  3056. // Check if we owned the old container too, so we don't need to do anything,
  3057. // as the list was updated in postRemoveNotification
  3058. //assert(i ? i->getContainer() != nullptr : true);
  3059.  
  3060. /*if (i) {
  3061. requireListUpdate = i->getContainer()->getHoldingPlayer() != this;
  3062. } else {
  3063. requireListUpdate = oldParent != this;
  3064. }*/
  3065.  
  3066. updateInventoryWeight();
  3067. updateItemsLight();
  3068. sendStats();
  3069. }
  3070.  
  3071. if (const Item* item = thing->getItem()) {
  3072. if (const Container* container = item->getContainer()) {
  3073. onSendContainer(container);
  3074. }
  3075.  
  3076. /*if (shopOwner && requireListUpdate) {
  3077. updateSaleShopList(item);
  3078. }*/
  3079. } else if (const Creature* creature = thing->getCreature()) {
  3080. if (creature == this) {
  3081. //check containers
  3082. std::vector<Container*> containers;
  3083.  
  3084. for (const auto& it : openContainers) {
  3085. Container* container = it.second.container;
  3086. if (!Position::areInRange<1, 1, 0>(container->getPosition(), getPosition())) {
  3087. containers.push_back(container);
  3088. }
  3089. }
  3090.  
  3091. for (const Container* container : containers) {
  3092. autoCloseContainers(container);
  3093. }
  3094. }
  3095. }
  3096. }
  3097.  
  3098. void Player::postRemoveNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/)
  3099. {
  3100. if (link == LINK_OWNER) {
  3101. //calling movement scripts
  3102. g_moveEvents->onPlayerDeEquip(this, thing->getItem(), static_cast<slots_t>(index));
  3103. }
  3104.  
  3105. //bool requireListUpdate = false;
  3106.  
  3107. if (link == LINK_OWNER || link == LINK_TOPPARENT) {
  3108. //const Item* i = (newParent ? newParent->getItem() : nullptr);
  3109.  
  3110. // Check if we owned the old container too, so we don't need to do anything,
  3111. // as the list was updated in postRemoveNotification
  3112. //assert(i ? i->getContainer() != nullptr : true);
  3113.  
  3114. /*if (i) {
  3115. requireListUpdate = i->getContainer()->getHoldingPlayer() != this;
  3116. } else {
  3117. requireListUpdate = newParent != this;
  3118. }*/
  3119.  
  3120. updateInventoryWeight();
  3121. updateItemsLight();
  3122. sendStats();
  3123. }
  3124.  
  3125. if (const Item* item = thing->getItem()) {
  3126. if (const Container* container = item->getContainer()) {
  3127. if (container->isRemoved() || !Position::areInRange<1, 1, 0>(getPosition(), container->getPosition())) {
  3128. autoCloseContainers(container);
  3129. } else if (container->getTopParent() == this) {
  3130. onSendContainer(container);
  3131. } else if (const Container* topContainer = dynamic_cast<const Container*>(container->getTopParent())) {
  3132. if (const DepotChest* depotChest = dynamic_cast<const DepotChest*>(topContainer)) {
  3133. bool isOwner = false;
  3134.  
  3135. for (const auto& it : depotChests) {
  3136. if (it.second == depotChest) {
  3137. isOwner = true;
  3138. onSendContainer(container);
  3139. }
  3140. }
  3141.  
  3142. if (!isOwner) {
  3143. autoCloseContainers(container);
  3144. }
  3145. } else {
  3146. onSendContainer(container);
  3147. }
  3148. } else {
  3149. autoCloseContainers(container);
  3150. }
  3151. }
  3152.  
  3153. /*if (shopOwner && requireListUpdate) {
  3154. updateSaleShopList(item);
  3155. }*/
  3156. }
  3157. }
  3158.  
  3159. bool Player::updateSaleShopList(const Item* item)
  3160. {
  3161. uint16_t itemId = item->getID();
  3162. bool isCurrency = false;
  3163. for (const auto& it : Item::items.currencyItems) {
  3164. if (it.second == itemId) {
  3165. isCurrency = true;
  3166. break;
  3167. }
  3168. }
  3169.  
  3170. if (!isCurrency) {
  3171. auto it = std::find_if(shopItemList.begin(), shopItemList.end(), [itemId](const ShopInfo& shopInfo) { return shopInfo.itemId == itemId && shopInfo.sellPrice != 0; });
  3172. if (it == shopItemList.end()) {
  3173. const Container* container = item->getContainer();
  3174. if (!container) {
  3175. return false;
  3176. }
  3177.  
  3178. const auto& items = container->getItemList();
  3179. return std::any_of(items.begin(), items.end(), [this](const Item* containerItem) {
  3180. return updateSaleShopList(containerItem);
  3181. });
  3182. }
  3183. }
  3184.  
  3185. if (client) {
  3186. client->sendSaleItemList(shopItemList);
  3187. }
  3188. return true;
  3189. }
  3190.  
  3191. bool Player::hasShopItemForSale(uint32_t itemId, uint8_t subType) const
  3192. {
  3193. const ItemType& itemType = Item::items[itemId];
  3194. return std::any_of(shopItemList.begin(), shopItemList.end(), [&](const ShopInfo& shopInfo) {
  3195. return shopInfo.itemId == itemId && shopInfo.buyPrice != 0 && (!itemType.isFluidContainer() || shopInfo.subType == subType);
  3196. });
  3197. }
  3198.  
  3199. void Player::internalAddThing(Thing* thing)
  3200. {
  3201. internalAddThing(0, thing);
  3202. }
  3203.  
  3204. void Player::internalAddThing(uint32_t index, Thing* thing)
  3205. {
  3206. Item* item = thing->getItem();
  3207. if (!item) {
  3208. return;
  3209. }
  3210.  
  3211. //index == 0 means we should equip this item at the most appropriate slot (no action required here)
  3212. if (index > CONST_SLOT_WHEREEVER && index <= CONST_SLOT_LAST) {
  3213. if (inventory[index]) {
  3214. return;
  3215. }
  3216.  
  3217. inventory[index] = item;
  3218. item->setParent(this);
  3219. }
  3220. }
  3221.  
  3222. bool Player::setFollowCreature(Creature* creature)
  3223. {
  3224. if (!Creature::setFollowCreature(creature)) {
  3225. setFollowCreature(nullptr);
  3226. setAttackedCreature(nullptr);
  3227.  
  3228. sendCancelMessage(RETURNVALUE_THEREISNOWAY);
  3229. sendCancelTarget();
  3230. stopWalk();
  3231. return false;
  3232. }
  3233. return true;
  3234. }
  3235.  
  3236. bool Player::setAttackedCreature(Creature* creature)
  3237. {
  3238. if (!Creature::setAttackedCreature(creature)) {
  3239. sendCancelTarget();
  3240. return false;
  3241. }
  3242.  
  3243. if (chaseMode && creature) {
  3244. if (followCreature != creature) {
  3245. //chase opponent
  3246. setFollowCreature(creature);
  3247. }
  3248. } else if (followCreature) {
  3249. setFollowCreature(nullptr);
  3250. }
  3251.  
  3252. if (creature) {
  3253. g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID())));
  3254. }
  3255. return true;
  3256. }
  3257.  
  3258. void Player::goToFollowCreature()
  3259. {
  3260. if (!walkTask) {
  3261. if ((OTSYS_TIME() - lastFailedFollow) < 2000) {
  3262. return;
  3263. }
  3264.  
  3265. Creature::goToFollowCreature();
  3266.  
  3267. if (followCreature && !hasFollowPath) {
  3268. lastFailedFollow = OTSYS_TIME();
  3269. }
  3270. }
  3271. }
  3272.  
  3273. void Player::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const
  3274. {
  3275. Creature::getPathSearchParams(creature, fpp);
  3276. fpp.fullPathSearch = true;
  3277. }
  3278.  
  3279. void Player::doAttacking(uint32_t)
  3280. {
  3281. if (lastAttack == 0) {
  3282. lastAttack = OTSYS_TIME() - getAttackSpeed() - 1;
  3283. }
  3284.  
  3285. if (hasCondition(CONDITION_PACIFIED)) {
  3286. return;
  3287. }
  3288.  
  3289. if ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()) {
  3290. bool result = false;
  3291.  
  3292. Item* tool = getWeapon();
  3293. const Weapon* weapon = g_weapons->getWeapon(tool);
  3294. uint32_t delay = getAttackSpeed();
  3295. bool classicSpeed = g_config.getBoolean(ConfigManager::CLASSIC_ATTACK_SPEED);
  3296.  
  3297. if (weapon) {
  3298. if (!weapon->interruptSwing()) {
  3299. result = weapon->useWeapon(this, tool, attackedCreature);
  3300. } else if (!classicSpeed && !canDoAction()) {
  3301. delay = getNextActionTime();
  3302. } else {
  3303. result = weapon->useWeapon(this, tool, attackedCreature);
  3304. }
  3305. } else {
  3306. result = Weapon::useFist(this, attackedCreature);
  3307. }
  3308.  
  3309. SchedulerTask* task = createSchedulerTask(std::max<uint32_t>(SCHEDULER_MINTICKS, delay), std::bind(&Game::checkCreatureAttack, &g_game, getID()));
  3310. if (!classicSpeed) {
  3311. setNextActionTask(task, false);
  3312. } else {
  3313. g_scheduler.addEvent(task);
  3314. }
  3315.  
  3316. if (result) {
  3317. lastAttack = OTSYS_TIME();
  3318. }
  3319. }
  3320. }
  3321.  
  3322. uint64_t Player::getGainedExperience(Creature* attacker) const
  3323. {
  3324. if (g_config.getBoolean(ConfigManager::EXPERIENCE_FROM_PLAYERS)) {
  3325. Player* attackerPlayer = attacker->getPlayer();
  3326. if (attackerPlayer && attackerPlayer != this && skillLoss && std::abs(static_cast<int32_t>(attackerPlayer->getLevel() - level)) <= g_config.getNumber(ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)) {
  3327. return std::max<uint64_t>(0, std::floor(getLostExperience() * getDamageRatio(attacker) * 0.75));
  3328. }
  3329. }
  3330. return 0;
  3331. }
  3332.  
  3333. void Player::onFollowCreature(const Creature* creature)
  3334. {
  3335. if (!creature) {
  3336. stopWalk();
  3337. }
  3338. }
  3339.  
  3340. void Player::setChaseMode(bool mode)
  3341. {
  3342. bool prevChaseMode = chaseMode;
  3343. chaseMode = mode;
  3344.  
  3345. if (prevChaseMode != chaseMode) {
  3346. if (chaseMode) {
  3347. if (!followCreature && attackedCreature) {
  3348. //chase opponent
  3349. setFollowCreature(attackedCreature);
  3350. }
  3351. } else if (attackedCreature) {
  3352. setFollowCreature(nullptr);
  3353. cancelNextWalk = true;
  3354. }
  3355. }
  3356. }
  3357.  
  3358. void Player::onWalkAborted()
  3359. {
  3360. setNextWalkActionTask(nullptr);
  3361. sendCancelWalk();
  3362. }
  3363.  
  3364. void Player::onWalkComplete()
  3365. {
  3366. if (walkTask) {
  3367. walkTaskEvent = g_scheduler.addEvent(walkTask);
  3368. walkTask = nullptr;
  3369. }
  3370. }
  3371.  
  3372. void Player::stopWalk()
  3373. {
  3374. cancelNextWalk = true;
  3375. }
  3376.  
  3377. LightInfo Player::getCreatureLight() const
  3378. {
  3379. if (internalLight.level > itemsLight.level) {
  3380. return internalLight;
  3381. }
  3382. return itemsLight;
  3383. }
  3384.  
  3385. void Player::updateItemsLight(bool internal /*=false*/)
  3386. {
  3387. LightInfo maxLight;
  3388.  
  3389. for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
  3390. Item* item = inventory[i];
  3391. if (item) {
  3392. LightInfo curLight = item->getLightInfo();
  3393.  
  3394. if (curLight.level > maxLight.level) {
  3395. maxLight = std::move(curLight);
  3396. }
  3397. }
  3398. }
  3399.  
  3400. if (itemsLight.level != maxLight.level || itemsLight.color != maxLight.color) {
  3401. itemsLight = maxLight;
  3402.  
  3403. if (!internal) {
  3404. g_game.changeLight(this);
  3405. }
  3406. }
  3407. }
  3408.  
  3409. void Player::onAddCondition(ConditionType_t type)
  3410. {
  3411. Creature::onAddCondition(type);
  3412.  
  3413. /*if (type == CONDITION_OUTFIT && isMounted()) {
  3414. dismount();
  3415. }*/
  3416.  
  3417. sendIcons();
  3418. }
  3419.  
  3420. void Player::onAddCombatCondition(ConditionType_t type)
  3421. {
  3422. switch (type) {
  3423. case CONDITION_POISON:
  3424. sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are poisoned.");
  3425. break;
  3426.  
  3427. case CONDITION_DROWN:
  3428. sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drowning.");
  3429. break;
  3430.  
  3431. case CONDITION_PARALYZE:
  3432. sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are paralyzed.");
  3433. break;
  3434.  
  3435. case CONDITION_DRUNK:
  3436. sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drunk.");
  3437. break;
  3438.  
  3439. case CONDITION_CURSED:
  3440. sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are cursed.");
  3441. break;
  3442.  
  3443. case CONDITION_FREEZING:
  3444. sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are freezing.");
  3445. break;
  3446.  
  3447. case CONDITION_DAZZLED:
  3448. sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are dazzled.");
  3449. break;
  3450.  
  3451. case CONDITION_BLEEDING:
  3452. sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are bleeding.");
  3453. break;
  3454.  
  3455. default:
  3456. break;
  3457. }
  3458. }
  3459.  
  3460. void Player::onEndCondition(ConditionType_t type)
  3461. {
  3462. Creature::onEndCondition(type);
  3463.  
  3464. if (type == CONDITION_INFIGHT) {
  3465. onIdleStatus();
  3466. pzLocked = false;
  3467. clearAttacked();
  3468.  
  3469. //if (getSkull() != SKULL_RED && getSkull() != SKULL_BLACK) {
  3470. if (getSkull() != SKULL_RED) {
  3471. setSkull(SKULL_NONE);
  3472. }
  3473. }
  3474.  
  3475. sendIcons();
  3476. }
  3477.  
  3478. void Player::onCombatRemoveCondition(Condition* condition)
  3479. {
  3480. //Creature::onCombatRemoveCondition(condition);
  3481. if (condition->getId() > 0) {
  3482. //Means the condition is from an item, id == slot
  3483. if (g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) {
  3484. Item* item = getInventoryItem(static_cast<slots_t>(condition->getId()));
  3485. if (item) {
  3486. //25% chance to destroy the item
  3487. if (25 >= uniform_random(1, 100)) {
  3488. g_game.internalRemoveItem(item);
  3489. }
  3490. }
  3491. }
  3492. } else {
  3493. if (!canDoAction()) {
  3494. const uint32_t delay = getNextActionTime();
  3495. const int32_t ticks = delay - (delay % EVENT_CREATURE_THINK_INTERVAL);
  3496. if (ticks < 0) {
  3497. removeCondition(condition);
  3498. } else {
  3499. condition->setTicks(ticks);
  3500. }
  3501. } else {
  3502. removeCondition(condition);
  3503. }
  3504. }
  3505. }
  3506.  
  3507. void Player::onAttackedCreature(Creature* target, bool addFightTicks /* = true */)
  3508. {
  3509. Creature::onAttackedCreature(target);
  3510.  
  3511. if (target->getZone() == ZONE_PVP) {
  3512. return;
  3513. }
  3514.  
  3515. if (target == this) {
  3516. if (addFightTicks) {
  3517. addInFightTicks();
  3518. }
  3519. return;
  3520. }
  3521.  
  3522. if (hasFlag(PlayerFlag_NotGainInFight)) {
  3523. return;
  3524. }
  3525.  
  3526. Player* targetPlayer = target->getPlayer();
  3527. if (targetPlayer && !isPartner(targetPlayer) && !isGuildMate(targetPlayer)) {
  3528. if (!pzLocked && g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) {
  3529. pzLocked = true;
  3530. sendIcons();
  3531. }
  3532.  
  3533. targetPlayer->addInFightTicks();
  3534.  
  3535. if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) {
  3536. addAttacked(targetPlayer);
  3537. targetPlayer->sendCreatureSkull(this);
  3538. } else if (!targetPlayer->hasAttacked(this)) {
  3539. if (!pzLocked) {
  3540. pzLocked = true;
  3541. sendIcons();
  3542. }
  3543.  
  3544. if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) {
  3545. addAttacked(targetPlayer);
  3546.  
  3547. if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) {
  3548. setSkull(SKULL_WHITE);
  3549. }
  3550.  
  3551. if (getSkull() == SKULL_NONE) {
  3552. targetPlayer->sendCreatureSkull(this);
  3553. }
  3554. }
  3555. }
  3556. }
  3557.  
  3558. if (addFightTicks) {
  3559. addInFightTicks();
  3560. }
  3561. }
  3562.  
  3563. void Player::onAttacked()
  3564. {
  3565. Creature::onAttacked();
  3566.  
  3567. addInFightTicks();
  3568. }
  3569.  
  3570. void Player::onIdleStatus()
  3571. {
  3572. Creature::onIdleStatus();
  3573.  
  3574. if (party) {
  3575. party->clearPlayerPoints(this);
  3576. }
  3577. }
  3578.  
  3579. void Player::onPlacedCreature()
  3580. {
  3581. //scripting event - onLogin
  3582. if (!g_creatureEvents->playerLogin(this)) {
  3583. kickPlayer(true);
  3584. }
  3585. }
  3586.  
  3587. void Player::onAttackedCreatureDrainHealth(Creature* target, int32_t points)
  3588. {
  3589. Creature::onAttackedCreatureDrainHealth(target, points);
  3590.  
  3591. if (target) {
  3592. if (party && !Combat::isPlayerCombat(target)) {
  3593. Monster* tmpMonster = target->getMonster();
  3594. if (tmpMonster && tmpMonster->isHostile()) {
  3595. //We have fulfilled a requirement for shared experience
  3596. party->updatePlayerTicks(this, points);
  3597. }
  3598. }
  3599. }
  3600. }
  3601.  
  3602. void Player::onTargetCreatureGainHealth(Creature* target, int32_t points)
  3603. {
  3604. if (target && party) {
  3605. Player* tmpPlayer = nullptr;
  3606.  
  3607. if (target->getPlayer()) {
  3608. tmpPlayer = target->getPlayer();
  3609. } else if (Creature* targetMaster = target->getMaster()) {
  3610. if (Player* targetMasterPlayer = targetMaster->getPlayer()) {
  3611. tmpPlayer = targetMasterPlayer;
  3612. }
  3613. }
  3614.  
  3615. if (isPartner(tmpPlayer)) {
  3616. party->updatePlayerTicks(this, points);
  3617. }
  3618. }
  3619. }
  3620.  
  3621. bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/)
  3622. {
  3623. bool unjustified = false;
  3624.  
  3625. if (hasFlag(PlayerFlag_NotGenerateLoot)) {
  3626. target->setDropLoot(false);
  3627. }
  3628.  
  3629. Creature::onKilledCreature(target, lastHit);
  3630.  
  3631. Player* targetPlayer = target->getPlayer();
  3632. if (!targetPlayer) {
  3633. return false;
  3634. }
  3635.  
  3636. if (targetPlayer->getZone() == ZONE_PVP) {
  3637. targetPlayer->setDropLoot(false);
  3638. targetPlayer->setSkillLoss(false);
  3639. } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) {
  3640. if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && !isGuildMate(targetPlayer) && targetPlayer != this) {
  3641. if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) {
  3642. unjustified = true;
  3643. addUnjustifiedDead(targetPlayer);
  3644. }
  3645.  
  3646. if (lastHit && hasCondition(CONDITION_INFIGHT)) {
  3647. pzLocked = true;
  3648. Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME) * 1000, 0);
  3649. addCondition(condition);
  3650. }
  3651. }
  3652. }
  3653.  
  3654. return unjustified;
  3655. }
  3656.  
  3657. void Player::gainExperience(uint64_t gainExp, Creature* source)
  3658. {
  3659. if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0 || staminaMinutes == 0) {
  3660. return;
  3661. }
  3662.  
  3663. addExperience(source, gainExp, true);
  3664. }
  3665.  
  3666. void Player::onGainExperience(uint64_t gainExp, Creature* target)
  3667. {
  3668. if (hasFlag(PlayerFlag_NotGainExperience)) {
  3669. return;
  3670. }
  3671.  
  3672. if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) {
  3673. party->shareExperience(gainExp, target);
  3674. //We will get a share of the experience through the sharing mechanism
  3675. return;
  3676. }
  3677.  
  3678. Creature::onGainExperience(gainExp, target);
  3679. gainExperience(gainExp, target);
  3680. }
  3681.  
  3682. void Player::onGainSharedExperience(uint64_t gainExp, Creature* source)
  3683. {
  3684. gainExperience(gainExp, source);
  3685. }
  3686.  
  3687. bool Player::isImmune(CombatType_t type) const
  3688. {
  3689. if (hasFlag(PlayerFlag_CannotBeAttacked)) {
  3690. return true;
  3691. }
  3692. return Creature::isImmune(type);
  3693. }
  3694.  
  3695. bool Player::isImmune(ConditionType_t type) const
  3696. {
  3697. if (hasFlag(PlayerFlag_CannotBeAttacked)) {
  3698. return true;
  3699. }
  3700. return Creature::isImmune(type);
  3701. }
  3702.  
  3703. bool Player::isAttackable() const
  3704. {
  3705. return !hasFlag(PlayerFlag_CannotBeAttacked);
  3706. }
  3707.  
  3708. bool Player::lastHitIsPlayer(Creature* lastHitCreature)
  3709. {
  3710. if (!lastHitCreature) {
  3711. return false;
  3712. }
  3713.  
  3714. if (lastHitCreature->getPlayer()) {
  3715. return true;
  3716. }
  3717.  
  3718. Creature* lastHitMaster = lastHitCreature->getMaster();
  3719. return lastHitMaster && lastHitMaster->getPlayer();
  3720. }
  3721.  
  3722. void Player::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/)
  3723. {
  3724. Creature::changeHealth(healthChange, sendHealthChange);
  3725. sendStats();
  3726. }
  3727.  
  3728. void Player::changeMana(int32_t manaChange)
  3729. {
  3730. if (!hasFlag(PlayerFlag_HasInfiniteMana)) {
  3731. if (manaChange > 0) {
  3732. mana += std::min<int32_t>(manaChange, getMaxMana() - mana);
  3733. } else {
  3734. mana = std::max<int32_t>(0, mana + manaChange);
  3735. }
  3736. }
  3737.  
  3738. sendStats();
  3739. }
  3740.  
  3741. void Player::changeSoul(int32_t soulChange)
  3742. {
  3743. if (soulChange > 0) {
  3744. soul += std::min<int32_t>(soulChange, vocation->getSoulMax() - soul);
  3745. } else {
  3746. soul = std::max<int32_t>(0, soul + soulChange);
  3747. }
  3748.  
  3749. sendStats();
  3750. }
  3751.  
  3752. bool Player::canWear(uint32_t lookType, uint8_t addons) const
  3753. {
  3754. if (group->access) {
  3755. return true;
  3756. }
  3757.  
  3758. const Outfit* outfit = Outfits::getInstance().getOutfitByLookType(sex, lookType);
  3759. if (!outfit) {
  3760. return false;
  3761. }
  3762.  
  3763. if (outfit->premium && !isPremium()) {
  3764. return false;
  3765. }
  3766.  
  3767. if (outfit->unlocked && addons == 0) {
  3768. return true;
  3769. }
  3770.  
  3771. for (const OutfitEntry& outfitEntry : outfits) {
  3772. if (outfitEntry.lookType == lookType) {
  3773. if (outfitEntry.addons == addons || outfitEntry.addons == 3 || addons == 0) {
  3774. return true;
  3775. }
  3776. return false; //have lookType on list and addons don't match
  3777. }
  3778. }
  3779. return false;
  3780. }
  3781.  
  3782. bool Player::hasOutfit(uint32_t lookType, uint8_t addons)
  3783. {
  3784. const Outfit* outfit = Outfits::getInstance().getOutfitByLookType(sex, lookType);
  3785. if (!outfit) {
  3786. return false;
  3787. }
  3788.  
  3789. if (outfit->unlocked && addons == 0) {
  3790. return true;
  3791. }
  3792.  
  3793. for (const OutfitEntry& outfitEntry : outfits) {
  3794. if (outfitEntry.lookType == lookType) {
  3795. if (outfitEntry.addons == addons || outfitEntry.addons == 3 || addons == 0){
  3796. return true;
  3797. }
  3798. return false; //have lookType on list and addons don't match
  3799. }
  3800. }
  3801. return false;
  3802. }
  3803.  
  3804. void Player::genReservedStorageRange()
  3805. {
  3806. //generate outfits range
  3807. uint32_t base_key = PSTRG_OUTFITS_RANGE_START;
  3808. for (const OutfitEntry& entry : outfits) {
  3809. storageMap[++base_key] = (entry.lookType << 16) | entry.addons;
  3810. }
  3811. }
  3812.  
  3813. void Player::addOutfit(uint16_t lookType, uint8_t addons)
  3814. {
  3815. for (OutfitEntry& outfitEntry : outfits) {
  3816. if (outfitEntry.lookType == lookType) {
  3817. outfitEntry.addons |= addons;
  3818. return;
  3819. }
  3820. }
  3821. outfits.emplace_back(lookType, addons);
  3822. }
  3823.  
  3824. bool Player::removeOutfit(uint16_t lookType)
  3825. {
  3826. for (auto it = outfits.begin(), end = outfits.end(); it != end; ++it) {
  3827. OutfitEntry& entry = *it;
  3828. if (entry.lookType == lookType) {
  3829. outfits.erase(it);
  3830. return true;
  3831. }
  3832. }
  3833. return false;
  3834. }
  3835.  
  3836. bool Player::removeOutfitAddon(uint16_t lookType, uint8_t addons)
  3837. {
  3838. for (OutfitEntry& outfitEntry : outfits) {
  3839. if (outfitEntry.lookType == lookType) {
  3840. outfitEntry.addons &= ~addons;
  3841. return true;
  3842. }
  3843. }
  3844. return false;
  3845. }
  3846.  
  3847. bool Player::getOutfitAddons(const Outfit& outfit, uint8_t& addons) const
  3848. {
  3849. if (group->access) {
  3850. addons = 3;
  3851. return true;
  3852. }
  3853.  
  3854. if (outfit.premium && !isPremium()) {
  3855. return false;
  3856. }
  3857.  
  3858. for (const OutfitEntry& outfitEntry : outfits) {
  3859. if (outfitEntry.lookType != outfit.lookType) {
  3860. continue;
  3861. }
  3862.  
  3863. addons = outfitEntry.addons;
  3864. return true;
  3865. }
  3866.  
  3867. if (!outfit.unlocked) {
  3868. return false;
  3869. }
  3870.  
  3871. addons = 0;
  3872. return true;
  3873. }
  3874.  
  3875. void Player::setSex(PlayerSex_t newSex)
  3876. {
  3877. sex = newSex;
  3878. }
  3879.  
  3880. Skulls_t Player::getSkull() const
  3881. {
  3882. if (hasFlag(PlayerFlag_NotGainInFight)) {
  3883. return SKULL_NONE;
  3884. }
  3885. return skull;
  3886. }
  3887.  
  3888. Skulls_t Player::getSkullClient(const Creature* creature) const
  3889. {
  3890. if (!creature || g_game.getWorldType() != WORLD_TYPE_PVP) {
  3891. return SKULL_NONE;
  3892. }
  3893.  
  3894. const Player* player = creature->getPlayer();
  3895. if (!player || player->getSkull() != SKULL_NONE) {
  3896. return Creature::getSkullClient(creature);
  3897. }
  3898.  
  3899. if (player->hasAttacked(this)) {
  3900. return SKULL_YELLOW;
  3901. }
  3902.  
  3903. if (isPartner(player)) {
  3904. return SKULL_GREEN;
  3905. }
  3906. return Creature::getSkullClient(creature);
  3907. }
  3908.  
  3909. bool Player::hasAttacked(const Player* attacked) const
  3910. {
  3911. if (hasFlag(PlayerFlag_NotGainInFight) || !attacked) {
  3912. return false;
  3913. }
  3914.  
  3915. return attackedSet.find(attacked->guid) != attackedSet.end();
  3916. }
  3917.  
  3918. void Player::addAttacked(const Player* attacked)
  3919. {
  3920. if (hasFlag(PlayerFlag_NotGainInFight) || !attacked || attacked == this) {
  3921. return;
  3922. }
  3923.  
  3924. attackedSet.insert(attacked->guid);
  3925. }
  3926.  
  3927. void Player::removeAttacked(const Player* attacked)
  3928. {
  3929. if (!attacked || attacked == this) {
  3930. return;
  3931. }
  3932.  
  3933. auto it = attackedSet.find(attacked->guid);
  3934. if (it != attackedSet.end()) {
  3935. attackedSet.erase(it);
  3936. }
  3937. }
  3938.  
  3939. void Player::clearAttacked()
  3940. {
  3941. attackedSet.clear();
  3942. }
  3943.  
  3944. void Player::addUnjustifiedDead(const Player* attacked)
  3945. {
  3946. if (hasFlag(PlayerFlag_NotGainInFight) || attacked == this || g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) {
  3947. return;
  3948. }
  3949.  
  3950. sendTextMessage(MESSAGE_EVENT_ADVANCE, "Warning! The murder of " + attacked->getName() + " was not justified.");
  3951.  
  3952. skullTicks += g_config.getNumber(ConfigManager::FRAG_TIME);
  3953.  
  3954. /*if (getSkull() != SKULL_BLACK) {
  3955. if (g_config.getNumber(ConfigManager::KILLS_TO_BLACK) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_BLACK) - 1) * static_cast<int64_t>(g_config.getNumber(ConfigManager::FRAG_TIME))) {
  3956. setSkull(SKULL_BLACK);
  3957. } else if (getSkull() != SKULL_RED && g_config.getNumber(ConfigManager::KILLS_TO_RED) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_RED) - 1) * static_cast<int64_t>(g_config.getNumber(ConfigManager::FRAG_TIME))) {
  3958. setSkull(SKULL_RED);
  3959. }
  3960. }*/
  3961.  
  3962. if (getSkull() != SKULL_RED && g_config.getNumber(ConfigManager::KILLS_TO_RED) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_RED) - 1) * static_cast<int64_t>(g_config.getNumber(ConfigManager::FRAG_TIME))) {
  3963. setSkull(SKULL_RED);
  3964. }
  3965. }
  3966.  
  3967. void Player::checkSkullTicks(int64_t ticks)
  3968. {
  3969. int64_t newTicks = skullTicks - ticks;
  3970. if (newTicks < 0) {
  3971. skullTicks = 0;
  3972. } else {
  3973. skullTicks = newTicks;
  3974. }
  3975.  
  3976. //if ((skull == SKULL_RED || skull == SKULL_BLACK) && skullTicks < 1 && !hasCondition(CONDITION_INFIGHT)) {
  3977. if (skull == SKULL_RED && skullTicks < 1 && !hasCondition(CONDITION_INFIGHT)) {
  3978. setSkull(SKULL_NONE);
  3979. }
  3980. }
  3981.  
  3982. bool Player::isPromoted() const
  3983. {
  3984. uint16_t promotedVocation = g_vocations.getPromotedVocation(vocation->getId());
  3985. return promotedVocation == VOCATION_NONE && vocation->getId() != promotedVocation;
  3986. }
  3987.  
  3988. double Player::getLostPercent() const
  3989. {
  3990. int32_t deathLosePercent = g_config.getNumber(ConfigManager::DEATH_LOSE_PERCENT);
  3991. if (deathLosePercent != -1) {
  3992. if (isPromoted()) {
  3993. deathLosePercent -= 3;
  3994. }
  3995.  
  3996. deathLosePercent -= blessings.count();
  3997. return std::max<int32_t>(0, deathLosePercent) / 100.;
  3998. }
  3999.  
  4000. double lossPercent;
  4001. if (level >= 25) {
  4002. double tmpLevel = level + (levelPercent / 100.);
  4003. lossPercent = static_cast<double>((tmpLevel + 50) * 50 * ((tmpLevel * tmpLevel) - (5 * tmpLevel) + 8)) / experience;
  4004. } else {
  4005. lossPercent = 10;
  4006. }
  4007.  
  4008. double percentReduction = 0;
  4009. if (isPromoted()) {
  4010. percentReduction += 30;
  4011. }
  4012. percentReduction += blessings.count() * 8;
  4013. return lossPercent * (1 - (percentReduction / 100.)) / 100.;
  4014. }
  4015.  
  4016. void Player::learnInstantSpell(const std::string& spellName)
  4017. {
  4018. if (!hasLearnedInstantSpell(spellName)) {
  4019. learnedInstantSpellList.push_front(spellName);
  4020. }
  4021. }
  4022.  
  4023. void Player::forgetInstantSpell(const std::string& spellName)
  4024. {
  4025. learnedInstantSpellList.remove(spellName);
  4026. }
  4027.  
  4028. bool Player::hasLearnedInstantSpell(const std::string& spellName) const
  4029. {
  4030. if (hasFlag(PlayerFlag_CannotUseSpells)) {
  4031. return false;
  4032. }
  4033.  
  4034. if (hasFlag(PlayerFlag_IgnoreSpellCheck)) {
  4035. return true;
  4036. }
  4037.  
  4038. for (const auto& learnedSpellName : learnedInstantSpellList) {
  4039. if (strcasecmp(learnedSpellName.c_str(), spellName.c_str()) == 0) {
  4040. return true;
  4041. }
  4042. }
  4043. return false;
  4044. }
  4045.  
  4046. bool Player::isInWar(const Player* player) const
  4047. {
  4048. if (!player || !guild) {
  4049. return false;
  4050. }
  4051.  
  4052. const Guild* playerGuild = player->getGuild();
  4053. if (!playerGuild) {
  4054. return false;
  4055. }
  4056.  
  4057. return isInWarList(playerGuild->getId()) && player->isInWarList(guild->getId());
  4058. }
  4059.  
  4060. bool Player::isInWarList(uint32_t guildId) const
  4061. {
  4062. return std::find(guildWarVector.begin(), guildWarVector.end(), guildId) != guildWarVector.end();
  4063. }
  4064.  
  4065. bool Player::isPremium() const
  4066. {
  4067. if (g_config.getBoolean(ConfigManager::FREE_PREMIUM) || hasFlag(PlayerFlag_IsAlwaysPremium)) {
  4068. return true;
  4069. }
  4070.  
  4071. return premiumEndsAt > time(nullptr);
  4072. }
  4073.  
  4074. void Player::setPremiumTime(time_t premiumEndsAt)
  4075. {
  4076. this->premiumEndsAt = premiumEndsAt;
  4077. //sendBasicData();
  4078. }
  4079.  
  4080. PartyShields_t Player::getPartyShield(const Player* player) const
  4081. {
  4082. if (!player) {
  4083. return SHIELD_NONE;
  4084. }
  4085.  
  4086. if (party) {
  4087. if (party->getLeader() == player) {
  4088. if (party->isSharedExperienceActive()) {
  4089. if (party->isSharedExperienceEnabled()) {
  4090. return SHIELD_YELLOW_SHAREDEXP;
  4091. }
  4092.  
  4093. if (party->canUseSharedExperience(player)) {
  4094. return SHIELD_YELLOW_NOSHAREDEXP;
  4095. }
  4096.  
  4097. return SHIELD_YELLOW_NOSHAREDEXP_BLINK;
  4098. }
  4099.  
  4100. return SHIELD_YELLOW;
  4101. }
  4102.  
  4103. if (player->party == party) {
  4104. if (party->isSharedExperienceActive()) {
  4105. if (party->isSharedExperienceEnabled()) {
  4106. return SHIELD_BLUE_SHAREDEXP;
  4107. }
  4108.  
  4109. if (party->canUseSharedExperience(player)) {
  4110. return SHIELD_BLUE_NOSHAREDEXP;
  4111. }
  4112.  
  4113. return SHIELD_BLUE_NOSHAREDEXP_BLINK;
  4114. }
  4115.  
  4116. return SHIELD_BLUE;
  4117. }
  4118.  
  4119. if (isInviting(player)) {
  4120. return SHIELD_WHITEBLUE;
  4121. }
  4122. }
  4123.  
  4124. if (player->isInviting(this)) {
  4125. return SHIELD_WHITEYELLOW;
  4126. }
  4127.  
  4128. /*if (player->party) {
  4129. return SHIELD_GRAY;
  4130. }*/
  4131.  
  4132. return SHIELD_NONE;
  4133. }
  4134.  
  4135. bool Player::isInviting(const Player* player) const
  4136. {
  4137. if (!player || !party || party->getLeader() != this) {
  4138. return false;
  4139. }
  4140. return party->isPlayerInvited(player);
  4141. }
  4142.  
  4143. bool Player::isPartner(const Player* player) const
  4144. {
  4145. if (!player || !party || player == this) {
  4146. return false;
  4147. }
  4148. return party == player->party;
  4149. }
  4150.  
  4151. bool Player::isGuildMate(const Player* player) const
  4152. {
  4153. if (!player || !guild) {
  4154. return false;
  4155. }
  4156. return guild == player->guild;
  4157. }
  4158.  
  4159. void Player::sendPlayerPartyIcons(Player* player)
  4160. {
  4161. sendCreatureShield(player);
  4162. sendCreatureSkull(player);
  4163. }
  4164.  
  4165. bool Player::addPartyInvitation(Party* party)
  4166. {
  4167. auto it = std::find(invitePartyList.begin(), invitePartyList.end(), party);
  4168. if (it != invitePartyList.end()) {
  4169. return false;
  4170. }
  4171.  
  4172. invitePartyList.push_front(party);
  4173. return true;
  4174. }
  4175.  
  4176. void Player::removePartyInvitation(Party* party)
  4177. {
  4178. invitePartyList.remove(party);
  4179. }
  4180.  
  4181. void Player::clearPartyInvitations()
  4182. {
  4183. for (Party* invitingParty : invitePartyList) {
  4184. invitingParty->removeInvite(*this, false);
  4185. }
  4186. invitePartyList.clear();
  4187. }
  4188.  
  4189. GuildEmblems_t Player::getGuildEmblem(const Player* player) const
  4190. {
  4191. if (!player) {
  4192. return GUILDEMBLEM_NONE;
  4193. }
  4194.  
  4195. const Guild* playerGuild = player->getGuild();
  4196. if (!playerGuild) {
  4197. return GUILDEMBLEM_NONE;
  4198. }
  4199.  
  4200. if (player->getGuildWarVector().empty()) {
  4201. if (guild == playerGuild) {
  4202. return GUILDEMBLEM_MEMBER;
  4203. } else {
  4204. return GUILDEMBLEM_OTHER;
  4205. }
  4206. } else if (guild == playerGuild) {
  4207. return GUILDEMBLEM_ALLY;
  4208. } else if (isInWar(player)) {
  4209. return GUILDEMBLEM_ENEMY;
  4210. }
  4211.  
  4212. return GUILDEMBLEM_NEUTRAL;
  4213. }
  4214.  
  4215. uint8_t Player::getCurrentMount() const
  4216. {
  4217. int32_t value;
  4218. if (getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, value)) {
  4219. return value;
  4220. }
  4221. return 0;
  4222. }
  4223.  
  4224. void Player::setCurrentMount(uint8_t mountId)
  4225. {
  4226. addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mountId);
  4227. }
  4228.  
  4229. bool Player::toggleMount(bool mount)
  4230. {
  4231. if ((OTSYS_TIME() - lastToggleMount) < 3000 && !wasMounted) {
  4232. sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
  4233. return false;
  4234. }
  4235.  
  4236. if (mount) {
  4237. if (isMounted()) {
  4238. return false;
  4239. }
  4240.  
  4241. if (!group->access && tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
  4242. sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE);
  4243. return false;
  4244. }
  4245.  
  4246. const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(getSex(), defaultOutfit.lookType);
  4247. if (!playerOutfit) {
  4248. return false;
  4249. }
  4250.  
  4251. uint8_t currentMountId = getCurrentMount();
  4252. if (currentMountId == 0) {
  4253. sendOutfitWindow();
  4254. return false;
  4255. }
  4256.  
  4257. Mount* currentMount = g_game.mounts.getMountByID(currentMountId);
  4258. if (!currentMount) {
  4259. return false;
  4260. }
  4261.  
  4262. if (!hasMount(currentMount)) {
  4263. setCurrentMount(0);
  4264. sendOutfitWindow();
  4265. return false;
  4266. }
  4267.  
  4268. if (currentMount->premium && !isPremium()) {
  4269. sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
  4270. return false;
  4271. }
  4272.  
  4273. if (hasCondition(CONDITION_OUTFIT)) {
  4274. sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
  4275. return false;
  4276. }
  4277.  
  4278. defaultOutfit.lookMount = currentMount->clientId;
  4279.  
  4280. if (currentMount->speed != 0) {
  4281. g_game.changeSpeed(this, currentMount->speed);
  4282. }
  4283. } else {
  4284. if (!isMounted()) {
  4285. return false;
  4286. }
  4287.  
  4288. dismount();
  4289. }
  4290.  
  4291. g_game.internalCreatureChangeOutfit(this, defaultOutfit);
  4292. lastToggleMount = OTSYS_TIME();
  4293. return true;
  4294. }
  4295.  
  4296. bool Player::tameMount(uint8_t mountId)
  4297. {
  4298. if (!g_game.mounts.getMountByID(mountId)) {
  4299. return false;
  4300. }
  4301.  
  4302. const uint8_t tmpMountId = mountId - 1;
  4303. const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31);
  4304.  
  4305. int32_t value;
  4306. if (getStorageValue(key, value)) {
  4307. value |= (1 << (tmpMountId % 31));
  4308. } else {
  4309. value = (1 << (tmpMountId % 31));
  4310. }
  4311.  
  4312. addStorageValue(key, value);
  4313. return true;
  4314. }
  4315.  
  4316. bool Player::untameMount(uint8_t mountId)
  4317. {
  4318. if (!g_game.mounts.getMountByID(mountId)) {
  4319. return false;
  4320. }
  4321.  
  4322. const uint8_t tmpMountId = mountId - 1;
  4323. const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31);
  4324.  
  4325. int32_t value;
  4326. if (!getStorageValue(key, value)) {
  4327. return true;
  4328. }
  4329.  
  4330. value &= ~(1 << (tmpMountId % 31));
  4331. addStorageValue(key, value);
  4332.  
  4333. if (getCurrentMount() == mountId) {
  4334. if (isMounted()) {
  4335. dismount();
  4336. g_game.internalCreatureChangeOutfit(this, defaultOutfit);
  4337. }
  4338.  
  4339. setCurrentMount(0);
  4340. }
  4341.  
  4342. return true;
  4343. }
  4344.  
  4345. bool Player::hasMount(const Mount* mount) const
  4346. {
  4347. if (isAccessPlayer()) {
  4348. return true;
  4349. }
  4350.  
  4351. if (mount->premium && !isPremium()) {
  4352. return false;
  4353. }
  4354.  
  4355. const uint8_t tmpMountId = mount->id - 1;
  4356.  
  4357. int32_t value;
  4358. if (!getStorageValue(PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31), value)) {
  4359. return false;
  4360. }
  4361.  
  4362. return ((1 << (tmpMountId % 31)) & value) != 0;
  4363. }
  4364.  
  4365. void Player::dismount()
  4366. {
  4367. Mount* mount = g_game.mounts.getMountByID(getCurrentMount());
  4368. if (mount && mount->speed > 0) {
  4369. g_game.changeSpeed(this, -mount->speed);
  4370. }
  4371.  
  4372. defaultOutfit.lookMount = 0;
  4373. }
  4374.  
  4375. bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries)
  4376. {
  4377. if (tries == 0 || skill == SKILL_LEVEL) {
  4378. return false;
  4379. }
  4380.  
  4381. bool sendUpdate = false;
  4382. uint32_t oldSkillValue, newSkillValue;
  4383. long double oldPercentToNextLevel, newPercentToNextLevel;
  4384.  
  4385. if (skill == SKILL_MAGLEVEL) {
  4386. uint64_t currReqMana = vocation->getReqMana(magLevel);
  4387. uint64_t nextReqMana = vocation->getReqMana(magLevel + 1);
  4388.  
  4389. if (currReqMana >= nextReqMana) {
  4390. return false;
  4391. }
  4392.  
  4393. oldSkillValue = magLevel;
  4394. oldPercentToNextLevel = static_cast<long double>(manaSpent * 100) / nextReqMana;
  4395.  
  4396. g_events->eventPlayerOnGainSkillTries(this, SKILL_MAGLEVEL, tries);
  4397. uint32_t currMagLevel = magLevel;
  4398.  
  4399. while ((manaSpent + tries) >= nextReqMana) {
  4400. tries -= nextReqMana - manaSpent;
  4401.  
  4402. magLevel++;
  4403. manaSpent = 0;
  4404.  
  4405. g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel);
  4406.  
  4407. sendUpdate = true;
  4408. currReqMana = nextReqMana;
  4409. nextReqMana = vocation->getReqMana(magLevel + 1);
  4410.  
  4411. if (currReqMana >= nextReqMana) {
  4412. tries = 0;
  4413. break;
  4414. }
  4415. }
  4416.  
  4417. manaSpent += tries;
  4418.  
  4419. if (magLevel != currMagLevel) {
  4420. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You advanced to magic level {:d}.", magLevel));
  4421. }
  4422.  
  4423. uint8_t newPercent;
  4424. if (nextReqMana > currReqMana) {
  4425. newPercent = Player::getPercentLevel(manaSpent, nextReqMana);
  4426. newPercentToNextLevel = static_cast<long double>(manaSpent * 100) / nextReqMana;
  4427. } else {
  4428. newPercent = 0;
  4429. newPercentToNextLevel = 0;
  4430. }
  4431.  
  4432. if (newPercent != magLevelPercent) {
  4433. magLevelPercent = newPercent;
  4434. sendUpdate = true;
  4435. }
  4436.  
  4437. newSkillValue = magLevel;
  4438. } else {
  4439. uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level);
  4440. uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1);
  4441. if (currReqTries >= nextReqTries) {
  4442. return false;
  4443. }
  4444.  
  4445. oldSkillValue = skills[skill].level;
  4446. oldPercentToNextLevel = static_cast<long double>(skills[skill].tries * 100) / nextReqTries;
  4447.  
  4448. g_events->eventPlayerOnGainSkillTries(this, skill, tries);
  4449. uint32_t currSkillLevel = skills[skill].level;
  4450.  
  4451. while ((skills[skill].tries + tries) >= nextReqTries) {
  4452. tries -= nextReqTries - skills[skill].tries;
  4453.  
  4454. skills[skill].level++;
  4455. skills[skill].tries = 0;
  4456. skills[skill].percent = 0;
  4457.  
  4458. g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level);
  4459.  
  4460. sendUpdate = true;
  4461. currReqTries = nextReqTries;
  4462. nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1);
  4463.  
  4464. if (currReqTries >= nextReqTries) {
  4465. tries = 0;
  4466. break;
  4467. }
  4468. }
  4469.  
  4470. skills[skill].tries += tries;
  4471.  
  4472. if (currSkillLevel != skills[skill].level) {
  4473. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You advanced to {:s} level {:d}.", getSkillName(skill), skills[skill].level));
  4474. }
  4475.  
  4476. uint8_t newPercent;
  4477. if (nextReqTries > currReqTries) {
  4478. newPercent = Player::getPercentLevel(skills[skill].tries, nextReqTries);
  4479. newPercentToNextLevel = static_cast<long double>(skills[skill].tries * 100) / nextReqTries;
  4480. } else {
  4481. newPercent = 0;
  4482. newPercentToNextLevel = 0;
  4483. }
  4484.  
  4485. if (skills[skill].percent != newPercent) {
  4486. skills[skill].percent = newPercent;
  4487. sendUpdate = true;
  4488. }
  4489.  
  4490. newSkillValue = skills[skill].level;
  4491. }
  4492.  
  4493. if (sendUpdate) {
  4494. sendSkills();
  4495. }
  4496.  
  4497. sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("Your {:s} skill changed from level {:d} (with {:.2f}% progress towards level {:d}) to level {:d} (with {:.2f}% progress towards level {:d})", ucwords(getSkillName(skill)), oldSkillValue, oldPercentToNextLevel, (oldSkillValue + 1), newSkillValue, newPercentToNextLevel, (newSkillValue + 1)));
  4498. return sendUpdate;
  4499. }
  4500.  
  4501. bool Player::hasModalWindowOpen(uint32_t modalWindowId) const
  4502. {
  4503. return find(modalWindows.begin(), modalWindows.end(), modalWindowId) != modalWindows.end();
  4504. }
  4505.  
  4506. void Player::onModalWindowHandled(uint32_t modalWindowId)
  4507. {
  4508. modalWindows.remove(modalWindowId);
  4509. }
  4510.  
  4511. void Player::sendModalWindow(const ModalWindow& modalWindow)
  4512. {
  4513. if (!client) {
  4514. return;
  4515. }
  4516.  
  4517. modalWindows.push_front(modalWindow.id);
  4518. client->sendModalWindow(modalWindow);
  4519. }
  4520.  
  4521. void Player::clearModalWindows()
  4522. {
  4523. modalWindows.clear();
  4524. }
  4525.  
  4526. uint16_t Player::getHelpers() const
  4527. {
  4528. uint16_t helpers;
  4529.  
  4530. if (guild && party) {
  4531. std::unordered_set<Player*> helperSet;
  4532.  
  4533. const auto& guildMembers = guild->getMembersOnline();
  4534. helperSet.insert(guildMembers.begin(), guildMembers.end());
  4535.  
  4536. const auto& partyMembers = party->getMembers();
  4537. helperSet.insert(partyMembers.begin(), partyMembers.end());
  4538.  
  4539. const auto& partyInvitees = party->getInvitees();
  4540. helperSet.insert(partyInvitees.begin(), partyInvitees.end());
  4541.  
  4542. helperSet.insert(party->getLeader());
  4543.  
  4544. helpers = helperSet.size();
  4545. } else if (guild) {
  4546. helpers = guild->getMembersOnline().size();
  4547. } else if (party) {
  4548. helpers = party->getMemberCount() + party->getInvitationCount() + 1;
  4549. } else {
  4550. helpers = 0;
  4551. }
  4552.  
  4553. return helpers;
  4554. }
  4555.  
  4556. void Player::sendClosePrivate(uint16_t channelId)
  4557. {
  4558. if (channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY) {
  4559. g_chat->removeUserFromChannel(*this, channelId);
  4560. }
  4561.  
  4562. if (client) {
  4563. client->sendClosePrivate(channelId);
  4564. }
  4565. }
  4566.  
  4567. uint64_t Player::getMoney() const
  4568. {
  4569. std::vector<const Container*> containers;
  4570. uint64_t moneyCount = 0;
  4571.  
  4572. for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
  4573. Item* item = inventory[i];
  4574. if (!item) {
  4575. continue;
  4576. }
  4577.  
  4578. const Container* container = item->getContainer();
  4579. if (container) {
  4580. containers.push_back(container);
  4581. } else {
  4582. moneyCount += item->getWorth();
  4583. }
  4584. }
  4585.  
  4586. size_t i = 0;
  4587. while (i < containers.size()) {
  4588. const Container* container = containers[i++];
  4589. for (const Item* item : container->getItemList()) {
  4590. const Container* tmpContainer = item->getContainer();
  4591. if (tmpContainer) {
  4592. containers.push_back(tmpContainer);
  4593. } else {
  4594. moneyCount += item->getWorth();
  4595. }
  4596. }
  4597. }
  4598. return moneyCount;
  4599. }
  4600.  
  4601. size_t Player::getMaxVIPEntries() const
  4602. {
  4603. if (group->maxVipEntries != 0) {
  4604. return group->maxVipEntries;
  4605. }
  4606.  
  4607. return g_config.getNumber(isPremium() ? ConfigManager::VIP_PREMIUM_LIMIT : ConfigManager::VIP_FREE_LIMIT);
  4608. }
  4609.  
  4610. size_t Player::getMaxDepotItems() const
  4611. {
  4612. if (group->maxDepotItems != 0) {
  4613. return group->maxDepotItems;
  4614. }
  4615.  
  4616. return g_config.getNumber(isPremium() ? ConfigManager::DEPOT_PREMIUM_LIMIT : ConfigManager::DEPOT_FREE_LIMIT);
  4617. }
  4618.  
  4619. std::forward_list<Condition*> Player::getMuteConditions() const
  4620. {
  4621. std::forward_list<Condition*> muteConditions;
  4622. for (Condition* condition : conditions) {
  4623. if (condition->getTicks() <= 0) {
  4624. continue;
  4625. }
  4626.  
  4627. ConditionType_t type = condition->getType();
  4628. if (type != CONDITION_MUTED && type != CONDITION_CHANNELMUTEDTICKS && type != CONDITION_YELLTICKS) {
  4629. continue;
  4630. }
  4631.  
  4632. muteConditions.push_front(condition);
  4633. }
  4634. return muteConditions;
  4635. }
  4636.  
  4637. void Player::setGuild(Guild* guild)
  4638. {
  4639. if (guild == this->guild) {
  4640. return;
  4641. }
  4642.  
  4643. Guild* oldGuild = this->guild;
  4644.  
  4645. this->guildNick.clear();
  4646. this->guild = nullptr;
  4647. this->guildRank = nullptr;
  4648.  
  4649. if (guild) {
  4650. GuildRank_ptr rank = guild->getRankByLevel(1);
  4651. if (!rank) {
  4652. return;
  4653. }
  4654.  
  4655. this->guild = guild;
  4656. this->guildRank = rank;
  4657. guild->addMember(this);
  4658. }
  4659.  
  4660. if (oldGuild) {
  4661. oldGuild->removeMember(this);
  4662. }
  4663. }
  4664.  
  4665. void Player::updateRegeneration()
  4666. {
  4667. if (!vocation) {
  4668. return;
  4669. }
  4670.  
  4671. Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT);
  4672. if (condition) {
  4673. condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->getHealthGainAmount());
  4674. condition->setParam(CONDITION_PARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000);
  4675. condition->setParam(CONDITION_PARAM_MANAGAIN, vocation->getManaGainAmount());
  4676. condition->setParam(CONDITION_PARAM_MANATICKS, vocation->getManaGainTicks() * 1000);
  4677. }
  4678. }
  4679.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement