/** * Tibia GIMUD Server - a free and open-source MMORPG server emulator * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "otpch.h" #include #include "bed.h" #include "chat.h" #include "combat.h" #include "configmanager.h" #include "creatureevent.h" #include "events.h" #include "game.h" #include "iologindata.h" #include "monster.h" #include "movement.h" #include "scheduler.h" extern ConfigManager g_config; extern Game g_game; extern Chat* g_chat; extern Vocations g_vocations; extern MoveEvents* g_moveEvents; extern CreatureEvents* g_creatureEvents; extern Events* g_events; MuteCountMap Player::muteCountMap; uint32_t Player::playerAutoID = 0x10000000; Player::Player(ProtocolGame_ptr p) : Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)) { } Player::~Player() { for (Item* item : inventory) { if (item) { item->setParent(nullptr); item->decrementReferenceCounter(); } } for (const auto& it : depotLockerMap) { it.second->decrementReferenceCounter(); } setWriteItem(nullptr); setEditHouse(nullptr); } bool Player::setVocation(uint16_t vocId) { Vocation* voc = g_vocations.getVocation(vocId); if (!voc) { return false; } vocation = voc; Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); if (condition) { condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->getHealthGainAmount()); condition->setParam(CONDITION_PARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000); condition->setParam(CONDITION_PARAM_MANAGAIN, vocation->getManaGainAmount()); condition->setParam(CONDITION_PARAM_MANATICKS, vocation->getManaGainTicks() * 1000); } return true; } bool Player::isPushable() const { if (hasFlag(PlayerFlag_CannotBePushed)) { return false; } return Creature::isPushable(); } std::string Player::getDescription(int32_t lookDistance) const { std::ostringstream s; if (lookDistance == -1) { s << "yourself."; if (group->access) { s << " You are " << group->name << '.'; } else if (vocation->getId() != VOCATION_NONE) { s << " You are " << vocation->getVocDescription() << '.'; } else { s << " You have no vocation."; } } else { s << name; if (!group->access) { s << " (Level " << level << ')'; } s << '.'; if (sex == PLAYERSEX_FEMALE) { s << " She"; } else { s << " He"; } if (group->access) { s << " is " << group->name << '.'; } else if (vocation->getId() != VOCATION_NONE) { s << " is " << vocation->getVocDescription() << '.'; } else { s << " has no vocation."; } } if (guild && guildRank) { if (lookDistance == -1) { s << " You are "; } else if (sex == PLAYERSEX_FEMALE) { s << " She is "; } else { s << " He is "; } s << guildRank->name << " of the " << guild->getName(); if (!guildNick.empty()) { s << " (" << guildNick << ')'; } s << "."; } return s.str(); } Item* Player::getInventoryItem(slots_t slot) const { if (slot < CONST_SLOT_FIRST || slot > CONST_SLOT_LAST) { return nullptr; } return inventory[slot]; } void Player::addConditionSuppressions(uint32_t conditions) { conditionSuppressions |= conditions; } void Player::removeConditionSuppressions(uint32_t conditions) { conditionSuppressions &= ~conditions; } Item* Player::getWeapon() const { Item* item = inventory[CONST_SLOT_LEFT]; if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { return item; } item = inventory[CONST_SLOT_RIGHT]; if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { return item; } return nullptr; } Item* Player::getAmmunition() const { return inventory[CONST_SLOT_AMMO]; } int32_t Player::getArmor() const { int32_t armor = 0; // base armor static const slots_t armorSlots[] = { CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING }; for (slots_t slot : armorSlots) { Item* inventoryItem = inventory[slot]; if (inventoryItem) { armor += inventoryItem->getArmor(); } } if (armor > 1) { armor = rand() % (armor >> 1) + (armor >> 1); } return armor; } void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const { shield = nullptr; weapon = nullptr; for (uint32_t slot = CONST_SLOT_RIGHT; slot <= CONST_SLOT_LEFT; slot++) { Item* item = inventory[slot]; if (!item) { continue; } switch (item->getWeaponType()) { case WEAPON_NONE: break; case WEAPON_SHIELD: { if (!shield || item->getDefense() > shield->getDefense()) { shield = item; } break; } default: { // weapons that are not shields weapon = item; break; } } } } int32_t Player::getDefense() { int32_t totalDefense = 5; int32_t defenseSkill = getSkillLevel(SKILL_FIST); const Item* weapon; const Item* shield; getShieldAndWeapon(shield, weapon); if (weapon) { totalDefense = weapon->getDefense() + 1; switch (weapon->getWeaponType()) { case WEAPON_AXE: defenseSkill = SKILL_AXE; break; case WEAPON_SWORD: defenseSkill = SKILL_SWORD; break; case WEAPON_CLUB: defenseSkill = SKILL_CLUB; break; case WEAPON_DISTANCE: case WEAPON_AMMO: defenseSkill = SKILL_DISTANCE; break; default: break; } } if (shield) { totalDefense = shield->getDefense() + 1; defenseSkill = getSkillLevel(SKILL_SHIELD); } fightMode_t attackMode = getFightMode(); if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { attackMode = FIGHTMODE_DEFENSE; } if (attackMode == FIGHTMODE_ATTACK) { totalDefense -= 4 * totalDefense / 10; } else if (attackMode == FIGHTMODE_DEFENSE) { totalDefense += 8 * totalDefense / 10; } if (totalDefense) { int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; int32_t randresult = rand() % 100; totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; } return totalDefense; } fightMode_t Player::getFightMode() const { return fightMode; } uint16_t Player::getClientIcons() const { uint16_t icons = 0; for (Condition* condition : conditions) { if (!isSuppress(condition->getType())) { icons |= condition->getIcons(); } } // Game client debugs with 10 or more icons // so let's prevent that from happening. std::bitset<20> icon_bitset(static_cast(icons)); for (size_t pos = 0, bits_set = icon_bitset.count(); bits_set >= 10; ++pos) { if (icon_bitset[pos]) { icon_bitset.reset(pos); --bits_set; } } return icon_bitset.to_ulong(); } void Player::updateInventoryWeight() { if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { return; } inventoryWeight = 0; for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { const Item* item = inventory[i]; if (item) { inventoryWeight += item->getWeight(); } } } void Player::addSkillAdvance(skills_t skill, uint64_t count) { uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level); uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); if (currReqTries >= nextReqTries) { //player has reached max skill return; } g_events->eventPlayerOnGainSkillTries(this, skill, count); if (count == 0) { return; } bool sendUpdateSkills = false; while ((skills[skill].tries + count) >= nextReqTries) { count -= nextReqTries - skills[skill].tries; skills[skill].level++; skills[skill].tries = 0; skills[skill].percent = 0; std::ostringstream ss; ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill].level << '.'; sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level); sendUpdateSkills = true; currReqTries = nextReqTries; nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); if (currReqTries >= nextReqTries) { count = 0; break; } } skills[skill].tries += count; uint32_t newPercent; if (nextReqTries > currReqTries) { newPercent = Player::getPercentLevel(skills[skill].tries, nextReqTries); } else { newPercent = 0; } if (skills[skill].percent != newPercent) { skills[skill].percent = newPercent; sendUpdateSkills = true; } if (sendUpdateSkills) { sendSkills(); } } void Player::setVarStats(stats_t stat, int32_t modifier) { varStats[stat] += modifier; switch (stat) { case STAT_MAXHITPOINTS: { if (getHealth() > getMaxHealth()) { Creature::changeHealth(getMaxHealth() - getHealth()); } else { g_game.addCreatureHealth(this); } break; } case STAT_MAXMANAPOINTS: { if (getMana() > getMaxMana()) { changeMana(getMaxMana() - getMana()); } break; } default: { break; } } } int32_t Player::getDefaultStats(stats_t stat) const { switch (stat) { case STAT_MAXHITPOINTS: return healthMax; case STAT_MAXMANAPOINTS: return manaMax; case STAT_MAGICPOINTS: return getBaseMagicLevel(); default: return 0; } } void Player::addContainer(uint8_t cid, Container* container) { if (cid > 0xF) { return; } auto it = openContainers.find(cid); if (it != openContainers.end()) { OpenContainer& openContainer = it->second; openContainer.container = container; openContainer.index = 0; } else { OpenContainer openContainer; openContainer.container = container; openContainer.index = 0; openContainers[cid] = openContainer; } } void Player::closeContainer(uint8_t cid) { auto it = openContainers.find(cid); if (it == openContainers.end()) { return; } openContainers.erase(it); } void Player::setContainerIndex(uint8_t cid, uint16_t index) { auto it = openContainers.find(cid); if (it == openContainers.end()) { return; } it->second.index = index; } Container* Player::getContainerByID(uint8_t cid) { auto it = openContainers.find(cid); if (it == openContainers.end()) { return nullptr; } return it->second.container; } int8_t Player::getContainerID(const Container* container) const { for (const auto& it : openContainers) { if (it.second.container == container) { return it.first; } } return -1; } uint16_t Player::getContainerIndex(uint8_t cid) const { auto it = openContainers.find(cid); if (it == openContainers.end()) { return 0; } return it->second.index; } bool Player::canOpenCorpse(uint32_t ownerId) const { return getID() == ownerId || (party && party->canOpenCorpse(ownerId)); } uint16_t Player::getLookCorpse() const { if (sex == PLAYERSEX_FEMALE) { return ITEM_FEMALE_CORPSE; } else { return ITEM_MALE_CORPSE; } } void Player::addStorageValue(const uint32_t key, const int32_t value, const bool isLogin/* = false*/) { if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) { outfits.emplace_back( value >> 16, value & 0xFF ); return; } else { std::cout << "Warning: unknown reserved key: " << key << " player: " << getName() << std::endl; return; } } if (value != -1) { int32_t oldValue; getStorageValue(key, oldValue); storageMap[key] = value; if (!isLogin && g_game.getClientVersion() >= CLIENT_VERSION_790) { auto currentFrameTime = g_dispatcher.getDispatcherCycle(); if (lastQuestlogUpdate != currentFrameTime && g_game.quests.isQuestStorage(key, value, oldValue)) { lastQuestlogUpdate = currentFrameTime; sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your questlog has been updated."); } } } else { storageMap.erase(key); } } bool Player::getStorageValue(const uint32_t key, int32_t& value) const { auto it = storageMap.find(key); if (it == storageMap.end()) { value = 0; return false; } value = it->second; return true; } bool Player::canSee(const Position& pos) const { if (!client) { return false; } return client->canSee(pos); } bool Player::canSeeCreature(const Creature* creature) const { if (creature == this) { return true; } if (creature->isInGhostMode() && !group->access) { return false; } if (!creature->getPlayer() && !canSeeInvisibility() && creature->isInvisible()) { return false; } return true; } void Player::onReceiveMail(uint32_t townId) const { if (isNearDepotBox(townId)) { sendTextMessage(MESSAGE_EVENT_ADVANCE, "New mail has arrived."); } } bool Player::isNearDepotBox(uint32_t townId) const { const Position& pos = getPosition(); for (int32_t cx = -1; cx <= 1; ++cx) { for (int32_t cy = -1; cy <= 1; ++cy) { Tile* tile = g_game.map.getTile(pos.x + cx, pos.y + cy, pos.z); if (!tile) { continue; } if (DepotLocker* depotLocker = tile->getDepotLocker()) { if (depotLocker->getDepotId() == townId) { return true; } } } } return false; } DepotLocker* Player::getDepotLocker(uint32_t depotId, bool autoCreate) { auto it = depotLockerMap.find(depotId); if (it != depotLockerMap.end()) { return it->second; } if (autoCreate) { DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1); depotLocker->setDepotId(depotId); Item* depotItem = Item::CreateItem(ITEM_DEPOT); if (depotItem) { depotLocker->internalAddThing(depotItem); } depotLockerMap[depotId] = depotLocker; return depotLocker; } return nullptr; } void Player::sendCancelMessage(ReturnValue message) const { sendCancelMessage(getReturnMessage(message)); } void Player::sendStats() { if (client) { client->sendStats(); } } void Player::sendPing() { int64_t timeNow = OTSYS_TIME(); bool hasLostConnection = false; if ((timeNow - lastPing) >= 5000) { lastPing = timeNow; if (client) { client->sendPing(); } else { hasLostConnection = true; } } int64_t noPongTime = timeNow - lastPong; if ((hasLostConnection || noPongTime >= 7000) && attackedCreature && attackedCreature->getPlayer()) { setAttackedCreature(nullptr); } if (noPongTime >= 60000 && canLogout()) { if (g_creatureEvents->playerLogout(this)) { if (client) { client->logout(true, true); } else { g_game.removeCreature(this, true); } } } } Item* Player::getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen) { windowTextId = this->windowTextId; maxWriteLen = this->maxWriteLen; return writeItem; } void Player::setWriteItem(Item* item, uint16_t maxWriteLen /*= 0*/) { windowTextId++; if (writeItem) { writeItem->decrementReferenceCounter(); } if (item) { writeItem = item; this->maxWriteLen = maxWriteLen; writeItem->incrementReferenceCounter(); } else { writeItem = nullptr; this->maxWriteLen = 0; } } House* Player::getEditHouse(uint32_t& windowTextId, uint32_t& listId) { windowTextId = this->windowTextId; listId = this->editListId; return editHouse; } void Player::setEditHouse(House* house, uint32_t listId /*= 0*/) { windowTextId++; editHouse = house; editListId = listId; } void Player::sendHouseWindow(House* house, uint32_t listId) const { if (!client) { return; } std::string text; if (house->getAccessList(listId, text)) { client->sendHouseWindow(windowTextId, text); } } //container void Player::sendAddContainerItem(const Container* container, const Item* item) { if (!client) { return; } for (const auto& it : openContainers) { const OpenContainer& openContainer = it.second; if (openContainer.container != container) { continue; } if (openContainer.index >= container->capacity()) { item = container->getItemByIndex(openContainer.index - 1); } client->sendAddContainerItem(it.first, item); } } void Player::sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* newItem) { if (!client) { return; } for (const auto& it : openContainers) { const OpenContainer& openContainer = it.second; if (openContainer.container != container) { continue; } if (slot < openContainer.index) { continue; } uint16_t pageEnd = openContainer.index + container->capacity(); if (slot >= pageEnd) { continue; } client->sendUpdateContainerItem(it.first, slot, newItem); } } void Player::sendRemoveContainerItem(const Container* container, uint16_t slot) { if (!client) { return; } for (auto& it : openContainers) { OpenContainer& openContainer = it.second; if (openContainer.container != container) { continue; } uint16_t& firstIndex = openContainer.index; if (firstIndex > 0 && firstIndex >= container->size() - 1) { firstIndex -= container->capacity(); sendContainer(it.first, container, false, firstIndex); } client->sendRemoveContainerItem(it.first, std::max(slot, firstIndex)); } } void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, const ItemType& oldType, const Item* newItem, const ItemType& newType) { Creature::onUpdateTileItem(tile, pos, oldItem, oldType, newItem, newType); if (oldItem != newItem) { onRemoveTileItem(tile, pos, oldType, oldItem); } if (tradeState != TRADE_TRANSFER) { if (tradeItem && oldItem == tradeItem) { g_game.internalCloseTrade(this); } } } void Player::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, const Item* item) { Creature::onRemoveTileItem(tile, pos, iType, item); if (tradeState != TRADE_TRANSFER) { checkTradeState(item); if (tradeItem) { const Container* container = item->getContainer(); if (container && container->isHoldingItem(tradeItem)) { g_game.internalCloseTrade(this); } } } } void Player::onCreatureAppear(Creature* creature, bool isLogin) { Creature::onCreatureAppear(creature, isLogin); if (isLogin && creature == this) { for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { Item* item = inventory[slot]; if (item) { item->startDecaying(); g_moveEvents->onPlayerEquip(this, item, static_cast(slot), false); } } for (Condition* condition : storedConditionList) { addCondition(condition); } storedConditionList.clear(); BedItem* bed = g_game.getBedBySleeper(guid); if (bed) { bed->wakeUp(this); } std::cout << name << " has logged in." << std::endl; if (guild) { guild->addMember(this); } int32_t offlineTime; if (getLastLogout() != 0) { // Not counting more than 21 days to prevent overflow when multiplying with 1000 (for milliseconds). offlineTime = std::min(time(nullptr) - getLastLogout(), 86400 * 21); } else { offlineTime = 0; } for (Condition* condition : getMuteConditions()) { condition->setTicks(condition->getTicks() - (offlineTime * 1000)); if (condition->getTicks() <= 0) { removeCondition(condition); } } g_game.checkPlayersRecord(); IOLoginData::updateOnlineStatus(guid, true); } } void Player::onAttackedCreatureDisappear(bool isLogout) { sendCancelTarget(); if (!isLogout) { sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost."); } } void Player::onFollowCreatureDisappear(bool isLogout) { sendCancelTarget(); if (!isLogout) { sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost."); } } void Player::onChangeZone(ZoneType_t zone) { if (zone == ZONE_PROTECTION) { if (attackedCreature && !hasFlag(PlayerFlag_IgnoreProtectionZone)) { setAttackedCreature(nullptr); onAttackedCreatureDisappear(false); } } sendIcons(); } void Player::onAttackedCreatureChangeZone(ZoneType_t zone) { if (zone == ZONE_PROTECTION) { if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) { setAttackedCreature(nullptr); onAttackedCreatureDisappear(false); } } else if (zone == ZONE_NOPVP) { if (attackedCreature->getPlayer()) { if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) { setAttackedCreature(nullptr); onAttackedCreatureDisappear(false); } } } else if (zone == ZONE_NORMAL) { //attackedCreature can leave a pvp zone if not pzlocked if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) { if (attackedCreature->getPlayer()) { setAttackedCreature(nullptr); onAttackedCreatureDisappear(false); } } } } void Player::onRemoveCreature(Creature* creature, bool isLogout) { Creature::onRemoveCreature(creature, isLogout); if (creature == this) { if (isLogout) { loginPosition = getPosition(); } lastLogout = time(nullptr); if (eventWalk != 0) { setFollowCreature(nullptr); } if (tradePartner) { g_game.internalCloseTrade(this); } clearPartyInvitations(); if (party) { party->leaveParty(this); } g_chat->removeUserFromAllChannels(*this); std::cout << getName() << " has logged out." << std::endl; if (guild) { guild->removeMember(this); } IOLoginData::updateOnlineStatus(guid, false); bool saved = false; for (uint32_t tries = 0; tries < 3; ++tries) { if (IOLoginData::savePlayer(this)) { saved = true; break; } } if (!saved) { std::cout << "Error while saving player: " << getName() << std::endl; } } } void Player::onWalk(Direction& dir) { Creature::onWalk(dir); setNextActionTask(nullptr); // TODO: Find out if really nothing brokes. This is for allow players to heal and walk or walk and open backpacks //setNextAction(OTSYS_TIME() + getStepDuration(dir)); } void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) { Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); if (hasFollowPath && (creature == followCreature || (creature == this && followCreature))) { isUpdatingPath = false; g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); } if (creature != this) { return; } if (tradeState != TRADE_TRANSFER) { //check if we should close trade if (tradeItem && !Position::areInRange<1, 1, 0>(tradeItem->getPosition(), getPosition())) { g_game.internalCloseTrade(this); } if (tradePartner && !Position::areInRange<2, 2, 0>(tradePartner->getPosition(), getPosition())) { g_game.internalCloseTrade(this); } } if (party) { party->updateSharedExperience(); } if (teleport || oldPos.z != newPos.z) { int32_t ticks = g_config.getNumber(ConfigManager::STAIRHOP_DELAY); if (ticks > 0) { if (Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_PACIFIED, ticks, 0)) { addCondition(condition); } } } } //container void Player::onAddContainerItem(const Item* item) { checkTradeState(item); } void Player::onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem) { if (oldItem != newItem) { onRemoveContainerItem(container, oldItem); } if (tradeState != TRADE_TRANSFER) { checkTradeState(oldItem); } } void Player::onRemoveContainerItem(const Container* container, const Item* item) { if (tradeState != TRADE_TRANSFER) { checkTradeState(item); if (tradeItem) { if (tradeItem->getParent() != container && container->isHoldingItem(tradeItem)) { g_game.internalCloseTrade(this); } } } } void Player::onCloseContainer(const Container* container) { if (!client) { return; } for (const auto& it : openContainers) { if (it.second.container == container) { client->sendCloseContainer(it.first); } } } void Player::onSendContainer(const Container* container) { if (!client) { return; } bool hasParent = container->hasParent(); for (const auto& it : openContainers) { const OpenContainer& openContainer = it.second; if (openContainer.container == container) { client->sendContainer(it.first, container, hasParent, openContainer.index); } } } //inventory void Player::onUpdateInventoryItem(Item* oldItem, Item* newItem) { if (oldItem != newItem) { onRemoveInventoryItem(oldItem); } if (tradeState != TRADE_TRANSFER) { checkTradeState(oldItem); } } void Player::onRemoveInventoryItem(Item* item) { if (tradeState != TRADE_TRANSFER) { checkTradeState(item); if (tradeItem) { const Container* container = item->getContainer(); if (container && container->isHoldingItem(tradeItem)) { g_game.internalCloseTrade(this); } } } } void Player::checkTradeState(const Item* item) { if (!tradeItem || tradeState == TRADE_TRANSFER) { return; } if (tradeItem == item) { g_game.internalCloseTrade(this); } else { const Container* container = dynamic_cast(item->getParent()); while (container) { if (container == tradeItem) { g_game.internalCloseTrade(this); break; } container = dynamic_cast(container->getParent()); } } } void Player::setNextWalkActionTask(SchedulerTask* task) { if (walkTaskEvent != 0) { g_scheduler.stopEvent(walkTaskEvent); walkTaskEvent = 0; } delete walkTask; walkTask = task; } void Player::setNextWalkTask(SchedulerTask* task) { if (nextStepEvent != 0) { g_scheduler.stopEvent(nextStepEvent); nextStepEvent = 0; } if (task) { nextStepEvent = g_scheduler.addEvent(task); resetIdleTime(); } } void Player::setNextActionTask(SchedulerTask* task) { if (actionTaskEvent != 0) { g_scheduler.stopEvent(actionTaskEvent); actionTaskEvent = 0; } if (task) { actionTaskEvent = g_scheduler.addEvent(task); resetIdleTime(); } } uint32_t Player::getNextActionTime() const { return std::max(SCHEDULER_MINTICKS, nextAction - OTSYS_TIME()); } void Player::onThink(uint32_t interval) { Creature::onThink(interval); sendPing(); MessageBufferTicks += interval; if (MessageBufferTicks >= 1500) { MessageBufferTicks = 0; addMessageBuffer(); } lastWalkingTime += interval; if (!getTile()->hasFlag(TILESTATE_NOLOGOUT) && !isAccessPlayer()) { idleTime += interval; const int32_t kickAfterMinutes = g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES); if ((!pzLocked && OTSYS_TIME() - lastPong >= 60000) || idleTime > (kickAfterMinutes * 60000) + 60000) { kickPlayer(true); } else if (client && idleTime == 60000 * kickAfterMinutes) { std::ostringstream ss; ss << "You have been idle for " << kickAfterMinutes << " minutes. You will be disconnected in one minute if you are still idle then."; client->sendTextMessage(TextMessage(MESSAGE_STATUS_WARNING, ss.str())); } } if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { checkSkullTicks(); } addOfflineTrainingTime(interval); } uint32_t Player::isMuted() const { if (hasFlag(PlayerFlag_CannotBeMuted)) { return 0; } int32_t muteTicks = 0; for (Condition* condition : conditions) { if (condition->getType() == CONDITION_MUTED && condition->getTicks() > muteTicks) { muteTicks = condition->getTicks(); } } return static_cast(muteTicks) / 1000; } void Player::addMessageBuffer() { if (MessageBufferCount > 0 && g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER) != 0 && !hasFlag(PlayerFlag_CannotBeMuted)) { --MessageBufferCount; } } void Player::removeMessageBuffer() { if (hasFlag(PlayerFlag_CannotBeMuted)) { return; } const int32_t maxMessageBuffer = g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER); if (maxMessageBuffer != 0 && MessageBufferCount <= maxMessageBuffer + 1) { if (++MessageBufferCount > maxMessageBuffer) { uint32_t muteCount = 1; auto it = muteCountMap.find(guid); if (it != muteCountMap.end()) { muteCount = it->second; } uint32_t muteTime = 5 * muteCount * muteCount; muteCountMap[guid] = muteCount + 1; Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_MUTED, muteTime * 1000, 0); addCondition(condition); std::ostringstream ss; ss << "You are muted for " << muteTime << " seconds."; sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); } } } void Player::drainHealth(Creature* attacker, int32_t damage) { Creature::drainHealth(attacker, damage); sendStats(); } void Player::drainMana(Creature* attacker, int32_t manaLoss) { onAttacked(); changeMana(-manaLoss); if (attacker) { addDamagePoints(attacker, manaLoss); } sendStats(); } void Player::addManaSpent(uint64_t amount) { if (hasFlag(PlayerFlag_NotGainMana)) { return; } uint64_t currReqMana = vocation->getReqMana(magLevel); uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); if (currReqMana >= nextReqMana) { //player has reached max magic level return; } g_events->eventPlayerOnGainSkillTries(this, SKILL_MAGLEVEL, amount); if (amount == 0) { return; } bool sendUpdateStats = false; while ((manaSpent + amount) >= nextReqMana) { amount -= nextReqMana - manaSpent; magLevel++; manaSpent = 0; std::ostringstream ss; ss << "You advanced to magic level " << magLevel << '.'; sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel); sendUpdateStats = true; currReqMana = nextReqMana; nextReqMana = vocation->getReqMana(magLevel + 1); if (currReqMana >= nextReqMana) { return; } } manaSpent += amount; uint8_t oldPercent = magLevelPercent; if (nextReqMana > currReqMana) { magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana); } else { magLevelPercent = 0; } if (oldPercent != magLevelPercent) { sendUpdateStats = true; } if (sendUpdateStats) { sendStats(); } } void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = false*/) { uint64_t currLevelExp = Player::getExpForLevel(level); uint64_t nextLevelExp = Player::getExpForLevel(level + 1); uint64_t rawExp = exp; if (currLevelExp >= nextLevelExp) { //player has reached max level levelPercent = 0; sendStats(); return; } g_events->eventPlayerOnGainExperience(this, source, exp, rawExp); if (exp == 0) { return; } experience += exp; if (sendText) { g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(exp)); } uint32_t prevLevel = level; while (experience >= nextLevelExp) { ++level; healthMax += vocation->getHPGain(); health += vocation->getHPGain(); manaMax += vocation->getManaGain(); mana += vocation->getManaGain(); capacity += vocation->getCapGain(); currLevelExp = nextLevelExp; nextLevelExp = Player::getExpForLevel(level + 1); if (currLevelExp >= nextLevelExp) { //player has reached max level break; } } if (prevLevel != level) { updateBaseSpeed(); setBaseSpeed(getBaseSpeed()); g_game.changeSpeed(this, 0); g_game.addCreatureHealth(this); if (party) { party->updateSharedExperience(); } g_creatureEvents->playerAdvance(this, SKILL_LEVEL, prevLevel, level); std::ostringstream ss; ss << "You advanced from Level " << prevLevel << " to Level " << level << '.'; sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); } if (nextLevelExp > currLevelExp) { levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); } else { levelPercent = 0; } sendStats(); } void Player::removeExperience(uint64_t exp) { if (experience == 0 || exp == 0) { return; } experience = std::max(0, experience - exp); uint32_t oldLevel = level; uint64_t currLevelExp = Player::getExpForLevel(level); while (level > 1 && experience < currLevelExp) { --level; healthMax = std::max(150, std::max(0, healthMax - vocation->getHPGain())); manaMax = std::max(0, manaMax - vocation->getManaGain()); capacity = std::max(400, std::max(0, capacity - vocation->getCapGain())); currLevelExp = Player::getExpForLevel(level); } if (oldLevel != level) { health = healthMax; mana = manaMax; updateBaseSpeed(); setBaseSpeed(getBaseSpeed()); g_game.changeSpeed(this, 0); g_game.addCreatureHealth(this); if (party) { party->updateSharedExperience(); } std::ostringstream ss; ss << "You were downgraded from Level " << oldLevel << " to Level " << level << '.'; sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); } uint64_t nextLevelExp = Player::getExpForLevel(level + 1); if (nextLevelExp > currLevelExp) { levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); } else { levelPercent = 0; } sendStats(); } uint8_t Player::getPercentLevel(uint64_t count, uint64_t nextLevelCount) { if (nextLevelCount == 0) { return 0; } uint8_t result = (count * 100) / nextLevelCount; if (result > 100) { return 0; } return result; } uint16_t Player::getDropLootPercent() { return 10; } void Player::onBlockHit() { if (shieldBlockCount > 0) { --shieldBlockCount; if (hasShield()) { addSkillAdvance(SKILL_SHIELD, 1); } } } void Player::onAttackedCreatureBlockHit(BlockType_t blockType) { lastAttackBlockType = blockType; switch (blockType) { case BLOCK_NONE: { addAttackSkillPoint = true; bloodHitCount = 30; shieldBlockCount = 30; break; } case BLOCK_DEFENSE: case BLOCK_ARMOR: { //need to draw blood every 30 hits if (bloodHitCount > 0) { addAttackSkillPoint = true; --bloodHitCount; } else { addAttackSkillPoint = false; } break; } default: { addAttackSkillPoint = false; break; } } } bool Player::hasShield() const { Item* item = inventory[CONST_SLOT_LEFT]; if (item && item->getWeaponType() == WEAPON_SHIELD) { return true; } item = inventory[CONST_SLOT_RIGHT]; if (item && item->getWeaponType() == WEAPON_SHIELD) { return true; } return false; } BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/) { BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field); if (attacker) { sendCreatureSquare(attacker, SQ_COLOR_BLACK); } if (blockType != BLOCK_NONE) { return blockType; } if (damage > 0) { for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { if (!isItemAbilityEnabled(static_cast(slot))) { continue; } Item* item = inventory[slot]; if (!item) { continue; } const ItemType& it = Item::items[item->getID()]; if (it.abilities) { const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)]; if (absorbPercent != 0) { damage -= std::round(damage * (absorbPercent / 100.)); uint16_t charges = item->getCharges() - 1; if (charges != 0) { g_game.transformItem(item, item->getID(), charges); } else { g_game.internalRemoveItem(item); } } if (field) { const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)]; if (fieldAbsorbPercent != 0) { damage -= std::round(damage * (fieldAbsorbPercent / 100.)); uint16_t charges = item->getCharges(); if (charges != 0) { if (charges - 1 == 0) { g_game.internalRemoveItem(item); } else { g_game.transformItem(item, item->getID(), charges - 1); } } } } } } if (damage <= 0) { damage = 0; blockType = BLOCK_ARMOR; } } return blockType; } uint32_t Player::getIP() const { if (client) { return client->getIP(); } return 0; } void Player::dropLoot(Container* corpse, Creature*) { if (corpse && lootDrop) { Skulls_t playerSkull = getSkull(); if (inventory[CONST_SLOT_NECKLACE] && inventory[CONST_SLOT_NECKLACE]->getID() == ITEM_AMULETOFLOSS && playerSkull != SKULL_RED) { g_game.internalRemoveItem(inventory[CONST_SLOT_NECKLACE], 1); } else { for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { Item* item = inventory[i]; if (item) { if (playerSkull == SKULL_RED || item->getContainer() || uniform_random(1, 100) <= getDropLootPercent()) { g_game.internalMoveItem(this, corpse, INDEX_WHEREEVER, item, item->getItemCount(), 0); sendInventoryItem(static_cast(i), nullptr); } } } } } if (g_game.getClientVersion() >= CLIENT_VERSION_790) { if (!inventory[CONST_SLOT_BACKPACK]) { Item* bagItem = Item::CreateItem(ITEM_BAG, 1); if (bagItem) { g_game.internalPlayerAddItem(this, bagItem, false, CONST_SLOT_BACKPACK); } } } } void Player::death(Creature* lastHitCreature) { loginPosition = town->getTemplePosition(); if (skillLoss) { //Magic level loss uint64_t sumMana = 0; uint64_t lostMana = 0; //sum up all the mana for (uint32_t i = 1; i <= magLevel; ++i) { sumMana += vocation->getReqMana(i); } sumMana += manaSpent; double deathLossPercent = getLostPercent(); lostMana = static_cast(sumMana * deathLossPercent); while (lostMana > manaSpent && magLevel > 0) { lostMana -= manaSpent; manaSpent = vocation->getReqMana(magLevel); magLevel--; } manaSpent -= lostMana; uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); if (nextReqMana > vocation->getReqMana(magLevel)) { magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana); } else { magLevelPercent = 0; } //Skill loss for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill uint64_t sumSkillTries = 0; for (uint16_t c = 11; c <= skills[i].level; ++c) { //sum up all required tries for all skill levels sumSkillTries += vocation->getReqSkillTries(i, c); } sumSkillTries += skills[i].tries; uint32_t lostSkillTries = static_cast(sumSkillTries * deathLossPercent); while (lostSkillTries > skills[i].tries) { lostSkillTries -= skills[i].tries; if (skills[i].level <= 10) { skills[i].level = 10; skills[i].tries = 0; lostSkillTries = 0; break; } skills[i].tries = vocation->getReqSkillTries(i, skills[i].level); skills[i].level--; } skills[i].tries = std::max(0, skills[i].tries - lostSkillTries); skills[i].percent = Player::getPercentLevel(skills[i].tries, vocation->getReqSkillTries(i, skills[i].level)); } //Level loss uint64_t expLoss = static_cast(experience * deathLossPercent); g_events->eventPlayerOnLoseExperience(this, expLoss); if (expLoss != 0) { uint32_t oldLevel = level; experience -= expLoss; while (level > 1 && experience < Player::getExpForLevel(level)) { --level; healthMax = std::max(0, healthMax - vocation->getHPGain()); manaMax = std::max(0, manaMax - vocation->getManaGain()); capacity = std::max(0, capacity - vocation->getCapGain()); } if (oldLevel != level) { std::ostringstream ss; ss << "You were downgraded from Level " << oldLevel << " to Level " << level << '.'; sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); } uint64_t currLevelExp = Player::getExpForLevel(level); uint64_t nextLevelExp = Player::getExpForLevel(level + 1); if (nextLevelExp > currLevelExp) { levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); } else { levelPercent = 0; } } std::bitset<6> bitset(blessings); if (bitset[5]) { if (Player::lastHitIsPlayer(lastHitCreature)) { bitset.reset(5); blessings = bitset.to_ulong(); } else { blessings = 32; } } else { blessings = 0; } sendStats(); sendSkills(); health = healthMax; mana = manaMax; auto it = conditions.begin(), end = conditions.end(); while (it != end) { Condition* condition = *it; if (condition->isPersistent()) { it = conditions.erase(it); condition->endCondition(this); onEndCondition(condition->getType()); delete condition; } else { ++it; } } // Teleport newbies to newbie island if (g_config.getBoolean(ConfigManager::TELEPORT_NEWBIES)) { if (getVocationId() != VOCATION_NONE && level <= static_cast(g_config.getNumber(ConfigManager::NEWBIE_LEVEL_THRESHOLD))) { Town* newbieTown = g_game.map.towns.getTown(g_config.getNumber(ConfigManager::NEWBIE_TOWN)); if (newbieTown) { // Restart stats level = 1; experience = 0; levelPercent = 0; capacity = 400; health = 150; healthMax = 150; mana = 0; manaMax = 0; magLevel = 0; magLevelPercent = 0; manaSpent = 0; staminaMinutes = 3360; setVocation(0); // Restart skills for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill skills[i].level = 10; skills[i].tries = 0; skills[i].percent = 0; } // Restart town setTown(newbieTown); loginPosition = getTemplePosition(); // Restart first items lastLoginSaved = 0; lastLogout = 0; // Restart storages storageMap.clear(); outfits.clear(); // Restart items for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; slot++) { Item* item = inventory[slot]; if (item) { g_game.internalRemoveItem(item, item->getItemCount()); } } } else { std::cout << "[Warning - Player:death] Newbie teletransportation is enabled, newbie town does not exist." << std::endl; } } } } else { setSkillLoss(true); auto it = conditions.begin(), end = conditions.end(); while (it != end) { Condition* condition = *it; if (condition->isPersistent()) { it = conditions.erase(it); condition->endCondition(this); onEndCondition(condition->getType()); delete condition; } else { ++it; } } health = healthMax; g_game.internalTeleport(this, getTemplePosition(), true); g_game.addCreatureHealth(this); onThink(EVENT_CREATURE_THINK_INTERVAL); onIdleStatus(); sendStats(); } } bool Player::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) { if (getZone() != ZONE_PVP || !Player::lastHitIsPlayer(lastHitCreature)) { return Creature::dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); } setDropLoot(true); return false; } Item* Player::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) { Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature); if (corpse && corpse->getContainer()) { std::ostringstream ss; if (lastHitCreature) { ss << "You recognize " << getNameDescription() << ". " << (getSex() == PLAYERSEX_FEMALE ? "She" : "He") << " was killed by " << lastHitCreature->getNameDescription() << '.'; } else { ss << "You recognize " << getNameDescription() << '.'; } corpse->setSpecialDescription(ss.str()); } return corpse; } void Player::addInFightTicks(bool pzlock /*= false*/) { if (hasFlag(PlayerFlag_NotGainInFight)) { return; } if (pzlock) { pzLocked = true; } Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::PZ_LOCKED), 0); addCondition(condition); } void Player::removeList() { g_game.removePlayer(this); for (const auto& it : g_game.getPlayers()) { it.second->notifyStatusChange(this, VIPSTATUS_OFFLINE); } } void Player::addList() { for (const auto& it : g_game.getPlayers()) { it.second->notifyStatusChange(this, VIPSTATUS_ONLINE); } g_game.addPlayer(this); } void Player::kickPlayer(bool displayEffect) { g_creatureEvents->playerLogout(this); if (client) { client->logout(displayEffect, true); } else { g_game.removeCreature(this); } } void Player::notifyStatusChange(Player* loginPlayer, VipStatus_t status) { if (!client) { return; } auto it = VIPList.find(loginPlayer->guid); if (it == VIPList.end()) { return; } client->sendUpdatedVIPStatus(loginPlayer->guid, status); if (status == VIPSTATUS_ONLINE) { client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged in.")); } else if (status == VIPSTATUS_OFFLINE) { client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged out.")); } } bool Player::removeVIP(uint32_t vipGuid) { if (VIPList.erase(vipGuid) == 0) { return false; } IOLoginData::removeVIPEntry(accountNumber, vipGuid); return true; } bool Player::addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status) { if (guid == vipGuid) { sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add yourself."); return false; } if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add more buddies."); return false; } auto result = VIPList.insert(vipGuid); if (!result.second) { sendTextMessage(MESSAGE_STATUS_SMALL, "This player is already in your list."); return false; } IOLoginData::addVIPEntry(accountNumber, vipGuid); if (client) { client->sendVIP(vipGuid, vipName, status); } return true; } bool Player::addVIPInternal(uint32_t vipGuid) { if (guid == vipGuid) { return false; } if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { return false; } return VIPList.insert(vipGuid).second; } //close container and its child containers void Player::autoCloseContainers(const Container* container) { std::vector closeList; for (const auto& it : openContainers) { Container* tmpContainer = it.second.container; while (tmpContainer) { if (tmpContainer->isRemoved() || tmpContainer == container) { closeList.push_back(it.first); break; } tmpContainer = dynamic_cast(tmpContainer->getParent()); } } for (uint32_t containerId : closeList) { closeContainer(containerId); if (client) { client->sendCloseContainer(containerId); } } } bool Player::hasCapacity(const Item* item, uint32_t count) const { if (hasFlag(PlayerFlag_CannotPickupItem)) { return false; } if (hasFlag(PlayerFlag_HasInfiniteCapacity) || item->getTopParent() == this) { return true; } uint32_t itemWeight = item->getContainer() != nullptr ? item->getWeight() : item->getBaseWeight(); if (item->isStackable()) { itemWeight *= count; } return itemWeight <= getFreeCapacity(); } ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature*) const { const Item* item = thing.getItem(); if (item == nullptr) { return RETURNVALUE_NOTPOSSIBLE; } bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); if (childIsOwner) { //a child container is querying the player, just check if enough capacity bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); if (skipLimit || hasCapacity(item, count)) { return RETURNVALUE_NOERROR; } return RETURNVALUE_NOTENOUGHCAPACITY; } if (!item->isPickupable()) { return RETURNVALUE_CANNOTPICKUP; } ReturnValue ret = RETURNVALUE_NOERROR; const int32_t& slotPosition = item->getSlotPosition(); if ((slotPosition & SLOTP_HEAD) || (slotPosition & SLOTP_NECKLACE) || (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) || (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || (slotPosition & SLOTP_RING)) { ret = RETURNVALUE_CANNOTBEDRESSED; } else if (slotPosition & SLOTP_TWO_HAND) { ret = RETURNVALUE_PUTTHISOBJECTINBOTHHANDS; } else if ((slotPosition & SLOTP_RIGHT) || (slotPosition & SLOTP_LEFT)) { ret = RETURNVALUE_PUTTHISOBJECTINYOURHAND; } switch (index) { case CONST_SLOT_HEAD: { if (slotPosition & SLOTP_HEAD) { ret = RETURNVALUE_NOERROR; } break; } case CONST_SLOT_NECKLACE: { if (slotPosition & SLOTP_NECKLACE) { ret = RETURNVALUE_NOERROR; } break; } case CONST_SLOT_BACKPACK: { if (slotPosition & SLOTP_BACKPACK) { ret = RETURNVALUE_NOERROR; } break; } case CONST_SLOT_ARMOR: { if (slotPosition & SLOTP_ARMOR) { ret = RETURNVALUE_NOERROR; } break; } case CONST_SLOT_RIGHT: { if (slotPosition & SLOTP_RIGHT) { if (slotPosition & SLOTP_TWO_HAND) { if (inventory[CONST_SLOT_LEFT] && inventory[CONST_SLOT_LEFT] != item) { ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; } else { ret = RETURNVALUE_NOERROR; } } else if (inventory[CONST_SLOT_LEFT]) { const Item* leftItem = inventory[CONST_SLOT_LEFT]; WeaponType_t type = item->getWeaponType(), leftType = leftItem->getWeaponType(); if (leftItem->getSlotPosition() & SLOTP_TWO_HAND) { ret = RETURNVALUE_DROPTWOHANDEDITEM; } else if (item == leftItem && count == item->getItemCount()) { ret = RETURNVALUE_NOERROR; } else if (leftType == WEAPON_SHIELD && type == WEAPON_SHIELD) { ret = RETURNVALUE_CANONLYUSEONESHIELD; } else if (leftType == WEAPON_NONE || type == WEAPON_NONE || leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO || type == WEAPON_SHIELD || type == WEAPON_AMMO) { ret = RETURNVALUE_NOERROR; } else { ret = RETURNVALUE_CANONLYUSEONEWEAPON; } } else { ret = RETURNVALUE_NOERROR; } } break; } case CONST_SLOT_LEFT: { if (slotPosition & SLOTP_LEFT) { if (slotPosition & SLOTP_TWO_HAND) { if (inventory[CONST_SLOT_RIGHT] && inventory[CONST_SLOT_RIGHT] != item) { ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; } else { ret = RETURNVALUE_NOERROR; } } else if (inventory[CONST_SLOT_RIGHT]) { const Item* rightItem = inventory[CONST_SLOT_RIGHT]; WeaponType_t type = item->getWeaponType(), rightType = rightItem->getWeaponType(); if (rightItem->getSlotPosition() & SLOTP_TWO_HAND) { ret = RETURNVALUE_DROPTWOHANDEDITEM; } else if (item == rightItem && count == item->getItemCount()) { ret = RETURNVALUE_NOERROR; } else if (rightType == WEAPON_SHIELD && type == WEAPON_SHIELD) { ret = RETURNVALUE_CANONLYUSEONESHIELD; } else if (rightType == WEAPON_NONE || type == WEAPON_NONE || rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO || type == WEAPON_SHIELD || type == WEAPON_AMMO) { ret = RETURNVALUE_NOERROR; } else { ret = RETURNVALUE_CANONLYUSEONEWEAPON; } } else { ret = RETURNVALUE_NOERROR; } } break; } case CONST_SLOT_LEGS: { if (slotPosition & SLOTP_LEGS) { ret = RETURNVALUE_NOERROR; } break; } case CONST_SLOT_FEET: { if (slotPosition & SLOTP_FEET) { ret = RETURNVALUE_NOERROR; } break; } case CONST_SLOT_RING: { if (slotPosition & SLOTP_RING) { ret = RETURNVALUE_NOERROR; } break; } case CONST_SLOT_AMMO: { ret = RETURNVALUE_NOERROR; break; } case CONST_SLOT_WHEREEVER: case -1: ret = RETURNVALUE_NOTENOUGHROOM; break; default: ret = RETURNVALUE_NOTPOSSIBLE; break; } if (ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_NOTENOUGHROOM) { //need an exchange with source? const Item* inventoryItem = getInventoryItem(static_cast(index)); if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->getID() != item->getID())) { return RETURNVALUE_NEEDEXCHANGE; } //check if enough capacity if (!hasCapacity(item, count)) { return RETURNVALUE_NOTENOUGHCAPACITY; } if (!g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), static_cast(index), true)) { return RETURNVALUE_CANNOTBEDRESSED; } } return ret; } ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, uint32_t flags) const { const Item* item = thing.getItem(); if (item == nullptr) { maxQueryCount = 0; return RETURNVALUE_NOTPOSSIBLE; } if (index == INDEX_WHEREEVER) { uint32_t n = 0; for (int32_t slotIndex = CONST_SLOT_FIRST; slotIndex <= CONST_SLOT_LAST; ++slotIndex) { Item* inventoryItem = inventory[slotIndex]; if (inventoryItem) { if (Container* subContainer = inventoryItem->getContainer()) { uint32_t queryCount = 0; subContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags); n += queryCount; //iterate through all items, including sub-containers (deep search) for (ContainerIterator it = subContainer->iterator(); it.hasNext(); it.advance()) { if (Container* tmpContainer = (*it)->getContainer()) { queryCount = 0; tmpContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags); n += queryCount; } } } else if (inventoryItem->isStackable() && item->equals(inventoryItem) && inventoryItem->getItemCount() < 100) { uint32_t remainder = (100 - inventoryItem->getItemCount()); if (queryAdd(slotIndex, *item, remainder, flags) == RETURNVALUE_NOERROR) { n += remainder; } } } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot if (item->isStackable()) { n += 100; } else { ++n; } } } maxQueryCount = n; } else { const Item* destItem = nullptr; const Thing* destThing = getThing(index); if (destThing) { destItem = destThing->getItem(); } if (destItem) { if (destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) { maxQueryCount = 100 - destItem->getItemCount(); } else { maxQueryCount = 0; } } else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { //empty slot if (item->isStackable()) { maxQueryCount = 100; } else { maxQueryCount = 1; } return RETURNVALUE_NOERROR; } } if (maxQueryCount < count) { return RETURNVALUE_NOTENOUGHROOM; } else { return RETURNVALUE_NOERROR; } } ReturnValue Player::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const { int32_t index = getThingIndex(&thing); if (index == -1) { return RETURNVALUE_NOTPOSSIBLE; } const Item* item = thing.getItem(); if (item == nullptr) { return RETURNVALUE_NOTPOSSIBLE; } if (count == 0 || (item->isStackable() && count > item->getItemCount())) { return RETURNVALUE_NOTPOSSIBLE; } if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { return RETURNVALUE_NOTMOVEABLE; } return RETURNVALUE_NOERROR; } Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) { if (index == 0 /*drop to capacity window*/ || index == INDEX_WHEREEVER) { *destItem = nullptr; const Item* item = thing.getItem(); if (item == nullptr) { return this; } bool autoStack = !((flags & FLAG_IGNOREAUTOSTACK) == FLAG_IGNOREAUTOSTACK); bool isStackable = item->isStackable(); std::vector containers; for (uint32_t slotIndex = CONST_SLOT_FIRST; slotIndex <= CONST_SLOT_LAST; ++slotIndex) { Item* inventoryItem = inventory[slotIndex]; if (inventoryItem) { if (inventoryItem == tradeItem) { continue; } if (inventoryItem == item) { continue; } if (autoStack && isStackable) { //try find an already existing item to stack with if (queryAdd(slotIndex, *item, item->getItemCount(), 0) == RETURNVALUE_NOERROR) { if (inventoryItem->equals(item) && inventoryItem->getItemCount() < 100) { index = slotIndex; *destItem = inventoryItem; return this; } } if (Container* subContainer = inventoryItem->getContainer()) { containers.push_back(subContainer); } } else if (Container* subContainer = inventoryItem->getContainer()) { containers.push_back(subContainer); } } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot index = slotIndex; *destItem = nullptr; return this; } } size_t i = 0; while (i < containers.size()) { Container* tmpContainer = containers[i++]; if (!autoStack || !isStackable) { //we need to find first empty container as fast as we can for non-stackable items uint32_t n = tmpContainer->capacity() - tmpContainer->size(); while (n) { if (tmpContainer->queryAdd(tmpContainer->capacity() - n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { index = tmpContainer->capacity() - n; *destItem = nullptr; return tmpContainer; } n--; } if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) { for (Item* tmpContainerItem : tmpContainer->getItemList()) { if (Container* subContainer = tmpContainerItem->getContainer()) { containers.push_back(subContainer); } } } continue; } uint32_t n = 0; for (Item* tmpItem : tmpContainer->getItemList()) { if (tmpItem == tradeItem) { continue; } if (tmpItem == item) { continue; } //try find an already existing item to stack with if (tmpItem->equals(item) && tmpItem->getItemCount() < 100) { index = n; *destItem = tmpItem; return tmpContainer; } if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) { if (Container* subContainer = tmpItem->getContainer()) { containers.push_back(subContainer); } } n++; } if (n < tmpContainer->capacity() && tmpContainer->queryAdd(n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { index = n; *destItem = nullptr; return tmpContainer; } } return this; } Thing* destThing = getThing(index); if (destThing) { *destItem = destThing->getItem(); } Cylinder* subCylinder = dynamic_cast(destThing); if (subCylinder) { index = INDEX_WHEREEVER; *destItem = nullptr; return subCylinder; } else { return this; } } void Player::addThing(int32_t index, Thing* thing) { if (index < CONST_SLOT_FIRST || index > CONST_SLOT_LAST) { return /*RETURNVALUE_NOTPOSSIBLE*/; } Item* item = thing->getItem(); if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } item->setParent(this); inventory[index] = item; //send to client sendInventoryItem(static_cast(index), item); } void Player::updateThing(Thing* thing, uint16_t itemId, uint32_t count) { int32_t index = getThingIndex(thing); if (index == -1) { return /*RETURNVALUE_NOTPOSSIBLE*/; } Item* item = thing->getItem(); if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } item->setID(itemId); item->setSubType(count); //send to client sendInventoryItem(static_cast(index), item); //event methods onUpdateInventoryItem(item, item); } void Player::replaceThing(uint32_t index, Thing* thing) { if (index > CONST_SLOT_LAST) { return /*RETURNVALUE_NOTPOSSIBLE*/; } Item* oldItem = getInventoryItem(static_cast(index)); if (!oldItem) { return /*RETURNVALUE_NOTPOSSIBLE*/; } Item* item = thing->getItem(); if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } //send to client sendInventoryItem(static_cast(index), item); //event methods onUpdateInventoryItem(oldItem, item); item->setParent(this); inventory[index] = item; } void Player::removeThing(Thing* thing, uint32_t count) { Item* item = thing->getItem(); if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } int32_t index = getThingIndex(thing); if (index == -1) { return /*RETURNVALUE_NOTPOSSIBLE*/; } if (item->isStackable()) { if (count == item->getItemCount()) { //send change to client sendInventoryItem(static_cast(index), nullptr); //event methods onRemoveInventoryItem(item); item->setParent(nullptr); inventory[index] = nullptr; } else { uint8_t newCount = static_cast(std::max(0, item->getItemCount() - count)); item->setItemCount(newCount); //send change to client sendInventoryItem(static_cast(index), item); //event methods onUpdateInventoryItem(item, item); } } else { //send change to client sendInventoryItem(static_cast(index), nullptr); //event methods onRemoveInventoryItem(item); item->setParent(nullptr); inventory[index] = nullptr; } } int32_t Player::getThingIndex(const Thing* thing) const { for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { if (inventory[i] == thing) { return i; } } return -1; } size_t Player::getFirstIndex() const { return CONST_SLOT_FIRST; } size_t Player::getLastIndex() const { return CONST_SLOT_LAST + 1; } uint32_t Player::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const { uint32_t count = 0; for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { Item* item = inventory[i]; if (!item) { continue; } if (item->getID() == itemId) { count += Item::countByType(item, subType); } if (Container* container = item->getContainer()) { for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { if ((*it)->getID() == itemId) { count += Item::countByType(*it, subType); } } } } return count; } // 7.8 mechanics to count runes by charges requires different logic to be implemented uint32_t Player::getRuneCount(uint16_t itemId) const { uint32_t count = 0; for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { Item* item = inventory[i]; if (!item) { continue; } if (item->getID() == itemId) { count += item->getCharges(); } if (Container* container = item->getContainer()) { for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { if ((*it)->getID() == itemId) { count += (*it)->getCharges(); } } } } return count; } bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped/* = false*/) const { if (amount == 0) { return true; } std::vector itemList; uint32_t count = 0; for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { Item* item = inventory[i]; if (!item) { continue; } if (!ignoreEquipped && item->getID() == itemId) { uint32_t itemCount = Item::countByType(item, subType); if (itemCount == 0) { continue; } itemList.push_back(item); count += itemCount; if (count >= amount) { g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); return true; } } else if (Container* container = item->getContainer()) { if (container->getID() == itemId) { uint32_t itemCount = Item::countByType(item, subType); if (itemCount == 0) { continue; } itemList.push_back(item); count += itemCount; if (count >= amount) { g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); return true; } } for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { Item* containerItem = *it; if (containerItem->getID() == itemId) { uint32_t itemCount = Item::countByType(containerItem, subType); if (itemCount == 0) { continue; } itemList.push_back(containerItem); count += itemCount; if (count >= amount) { g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); return true; } } } } } return false; } std::map& Player::getAllItemTypeCount(std::map& countMap) const { for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { Item* item = inventory[i]; if (!item) { continue; } countMap[item->getID()] += Item::countByType(item, -1); if (Container* container = item->getContainer()) { for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { countMap[(*it)->getID()] += Item::countByType(*it, -1); } } } return countMap; } Thing* Player::getThing(size_t index) const { if (index >= CONST_SLOT_FIRST && index <= CONST_SLOT_LAST) { return inventory[index]; } return nullptr; } void Player::postAddNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) { if (link == LINK_OWNER) { //calling movement scripts g_moveEvents->onPlayerEquip(this, thing->getItem(), static_cast(index), false); } if (link == LINK_OWNER || link == LINK_TOPPARENT) { updateInventoryWeight(); updateItemsLight(); sendStats(); } if (const Item* item = thing->getItem()) { if (const Container* container = item->getContainer()) { onSendContainer(container); } } else if (const Creature* creature = thing->getCreature()) { if (creature == this) { //check containers std::vector containers; for (const auto& it : openContainers) { Container* container = it.second.container; if (!Position::areInRange<1, 1, 0>(container->getPosition(), getPosition())) { containers.push_back(container); } } for (const Container* container : containers) { autoCloseContainers(container); } } } } void Player::postRemoveNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) { if (link == LINK_OWNER) { //calling movement scripts g_moveEvents->onPlayerDeEquip(this, thing->getItem(), static_cast(index)); } if (link == LINK_OWNER || link == LINK_TOPPARENT) { updateInventoryWeight(); updateItemsLight(); sendStats(); } if (const Item* item = thing->getItem()) { if (const Container* container = item->getContainer()) { if (container->isRemoved() || !Position::areInRange<1, 1, 0>(getPosition(), container->getPosition())) { autoCloseContainers(container); } else if (container->getTopParent() == this) { onSendContainer(container); } else if (const Container* topContainer = dynamic_cast(container->getTopParent())) { if (const DepotLocker* depotLocker = dynamic_cast(topContainer)) { bool isOwner = false; for (const auto& it : depotLockerMap) { if (it.second == depotLocker) { isOwner = true; onSendContainer(container); } } if (!isOwner) { autoCloseContainers(container); } } else { onSendContainer(container); } } else { autoCloseContainers(container); } } } } void Player::internalAddThing(Thing* thing) { internalAddThing(0, thing); } void Player::internalAddThing(uint32_t index, Thing* thing) { Item* item = thing->getItem(); if (!item) { return; } //index == 0 means we should equip this item at the most appropiate slot (no action required here) if (index > 0 && index < 11) { if (inventory[index]) { return; } inventory[index] = item; item->setParent(this); } } uint32_t Player::checkPlayerKilling() { time_t today = std::time(nullptr); int32_t lastDay = 0; int32_t lastWeek = 0; int32_t lastMonth = 0; int64_t egibleMurders = 0; time_t dayTimestamp = today - (24 * 60 * 60); time_t weekTimestamp = today - (7 * 24 * 60 * 60); time_t monthTimestamp = today - (30 * 24 * 60 * 60); for (time_t currentMurderTimestamp : murderTimeStamps) { if (currentMurderTimestamp > dayTimestamp) { lastDay++; } if (currentMurderTimestamp > weekTimestamp) { lastWeek++; } egibleMurders = lastMonth + 1; if (currentMurderTimestamp <= monthTimestamp) { egibleMurders = lastMonth; } lastMonth = egibleMurders; } if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_BANISHMENT) || lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_BANISHMENT) || lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_BANISHMENT)) { return 2; // banishment! } if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_RED_SKULL) || lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_RED_SKULL) || lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_RED_SKULL)) { return 1; // red skull! } return 0; } bool Player::setFollowCreature(Creature* creature) { if (!Creature::setFollowCreature(creature)) { setFollowCreature(nullptr); setAttackedCreature(nullptr); sendCancelMessage(RETURNVALUE_THEREISNOWAY); sendCancelTarget(); stopWalk(); return false; } return true; } bool Player::setAttackedCreature(Creature* creature) { if (!Creature::setAttackedCreature(creature)) { sendCancelTarget(); return false; } if (chaseMode == CHASEMODE_FOLLOW && creature) { if (followCreature != creature) { //chase opponent setFollowCreature(creature); } } else if (followCreature) { setFollowCreature(nullptr); } if (creature) { g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); } return true; } void Player::goToFollowCreature() { if (!walkTask) { if ((OTSYS_TIME() - lastFailedFollow) < 2000) { return; } Creature::goToFollowCreature(); if (followCreature && !hasFollowPath) { lastFailedFollow = OTSYS_TIME(); } } } void Player::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const { Creature::getPathSearchParams(creature, fpp); fpp.fullPathSearch = true; } void Player::doAttacking(uint32_t) { if (lastAttack == 0) { lastAttack = OTSYS_TIME() - getAttackSpeed() - 1; } if (hasCondition(CONDITION_PACIFIED)) { return; } if ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()) { if (Combat::attack(this, attackedCreature)) { earliestAttackTime = OTSYS_TIME() + 2000; lastAttack = OTSYS_TIME(); } } } uint64_t Player::getGainedExperience(Creature* attacker) const { if (g_config.getBoolean(ConfigManager::EXPERIENCE_FROM_PLAYERS)) { Player* attackerPlayer = attacker->getPlayer(); if (attackerPlayer && attackerPlayer != this && skillLoss && std::abs(static_cast(attackerPlayer->getLevel() - level)) <= g_config.getNumber(ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)) { return std::max(0, std::floor(getLostExperience() * getDamageRatio(attacker) * 0.75)); } } return 0; } void Player::onFollowCreature(const Creature* creature) { if (!creature) { stopWalk(); } } void Player::setChaseMode(chaseMode_t mode) { chaseMode_t prevChaseMode = chaseMode; chaseMode = mode; if (prevChaseMode != chaseMode) { if (chaseMode == CHASEMODE_FOLLOW) { if (!followCreature && attackedCreature) { //chase opponent setFollowCreature(attackedCreature); } } else if (attackedCreature) { setFollowCreature(nullptr); cancelNextWalk = true; } } } void Player::onWalkAborted() { setNextWalkActionTask(nullptr); sendCancelWalk(); } void Player::onWalkComplete() { if (walkTask) { walkTaskEvent = g_scheduler.addEvent(walkTask); walkTask = nullptr; } } void Player::stopWalk() { cancelNextWalk = true; } LightInfo Player::getCreatureLight() const { if (internalLight.level > itemsLight.level) { return internalLight; } return itemsLight; } void Player::updateItemsLight(bool internal /*=false*/) { LightInfo maxLight; LightInfo curLight; for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { Item* item = inventory[i]; if (item) { item->getLight(curLight); if (curLight.level > maxLight.level) { maxLight = curLight; } } } if (itemsLight.level != maxLight.level || itemsLight.color != maxLight.color) { itemsLight = maxLight; if (!internal) { g_game.changeLight(this); } } } void Player::onAddCondition(ConditionType_t type) { Creature::onAddCondition(type); sendIcons(); } void Player::onAddCombatCondition(ConditionType_t type) { switch (type) { case CONDITION_POISON: sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are poisoned."); break; case CONDITION_DROWN: sendTextMessage(MESSAGE_STATUS_SMALL, "You are drowning."); break; case CONDITION_PARALYZE: sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are paralyzed."); break; case CONDITION_DRUNK: sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drunk."); break; default: break; } } void Player::onEndCondition(ConditionType_t type) { Creature::onEndCondition(type); if (type == CONDITION_INFIGHT) { onIdleStatus(); pzLocked = false; clearAttacked(); if (getSkull() != SKULL_RED) { setSkull(SKULL_NONE); } } sendIcons(); } void Player::onCombatRemoveCondition(Condition* condition) { //Creature::onCombatRemoveCondition(condition); if (condition->getId() > 0) { //Means the condition is from an item, id == slot if (g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { Item* item = getInventoryItem(static_cast(condition->getId())); if (item) { //25% chance to destroy the item if (25 >= uniform_random(1, 100)) { g_game.internalRemoveItem(item); } } } } else { if (!canDoAction()) { const uint32_t delay = getNextActionTime(); const int32_t ticks = delay - (delay % EVENT_CREATURE_THINK_INTERVAL); if (ticks < 0) { removeCondition(condition); } else { condition->setTicks(ticks); } } else { removeCondition(condition); } } } void Player::onAttackedCreature(Creature* target) { Creature::onAttackedCreature(target); if (target->getZone() == ZONE_PVP) { return; } if (target == this) { addInFightTicks(); return; } if (hasFlag(PlayerFlag_NotGainInFight)) { return; } Player* targetPlayer = target->getPlayer(); if (targetPlayer) { if (!pzLocked && g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { pzLocked = true; sendIcons(); } if (!isPartner(targetPlayer)) { if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { addAttacked(targetPlayer); targetPlayer->sendCreatureSkull(this); } else { if (!targetPlayer->hasAttacked(this)) { if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) { addAttacked(targetPlayer); if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) { setSkull(SKULL_WHITE); } } if (getSkull() == SKULL_NONE) { targetPlayer->sendCreatureSkull(this); } } } } } addInFightTicks(); } void Player::onAttacked() { Creature::onAttacked(); addInFightTicks(); } void Player::onIdleStatus() { Creature::onIdleStatus(); if (party) { party->clearPlayerPoints(this); } } void Player::onPlacedCreature() { //scripting event - onLogin if (!g_creatureEvents->playerLogin(this)) { kickPlayer(true); } } void Player::onAttackedCreatureDrainHealth(Creature* target, int32_t points) { Creature::onAttackedCreatureDrainHealth(target, points); if (target) { if (party && !Combat::isPlayerCombat(target)) { Monster* tmpMonster = target->getMonster(); if (tmpMonster && tmpMonster->isHostile()) { //We have fulfilled a requirement for shared experience party->updatePlayerTicks(this, points); } } } } void Player::onTargetCreatureGainHealth(Creature* target, int32_t points) { if (target && party) { Player* tmpPlayer = nullptr; if (target->getPlayer()) { tmpPlayer = target->getPlayer(); } else if (Creature* targetMaster = target->getMaster()) { if (Player* targetMasterPlayer = targetMaster->getPlayer()) { tmpPlayer = targetMasterPlayer; } } if (isPartner(tmpPlayer)) { party->updatePlayerTicks(this, points); } } } bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) { bool unjustified = false; if (hasFlag(PlayerFlag_NotGenerateLoot)) { target->setDropLoot(false); } Creature::onKilledCreature(target, lastHit); if (Player* targetPlayer = target->getPlayer()) { if (targetPlayer && targetPlayer->getZone() == ZONE_PVP) { targetPlayer->setDropLoot(false); targetPlayer->setSkillLoss(false); } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && targetPlayer != this) { if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { unjustified = true; addUnjustifiedDead(targetPlayer); } } } if (lastHit && hasCondition(CONDITION_INFIGHT)) { pzLocked = true; Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME) * 1000, 0); addCondition(condition); } } return unjustified; } void Player::gainExperience(uint64_t gainExp, Creature* source) { if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0 || staminaMinutes == 0) { return; } addExperience(source, gainExp, true); } void Player::onGainExperience(uint64_t gainExp, Creature* target) { if (hasFlag(PlayerFlag_NotGainExperience)) { return; } if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { party->shareExperience(gainExp, target); //We will get a share of the experience through the sharing mechanism return; } Creature::onGainExperience(gainExp, target); gainExperience(gainExp, target); } void Player::onGainSharedExperience(uint64_t gainExp, Creature* source) { gainExperience(gainExp, source); } bool Player::isImmune(CombatType_t type) const { if (hasFlag(PlayerFlag_CannotBeAttacked)) { return true; } return Creature::isImmune(type); } bool Player::isImmune(ConditionType_t type) const { if (hasFlag(PlayerFlag_CannotBeAttacked)) { return true; } return Creature::isImmune(type); } bool Player::isAttackable() const { return !hasFlag(PlayerFlag_CannotBeAttacked); } bool Player::lastHitIsPlayer(Creature* lastHitCreature) { if (!lastHitCreature) { return false; } if (lastHitCreature->getPlayer()) { return true; } Creature* lastHitMaster = lastHitCreature->getMaster(); return lastHitMaster && lastHitMaster->getPlayer(); } void Player::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) { Creature::changeHealth(healthChange, sendHealthChange); sendStats(); } void Player::changeMana(int32_t manaChange) { if (!hasFlag(PlayerFlag_HasInfiniteMana)) { if (manaChange > 0) { mana += std::min(manaChange, getMaxMana() - mana); } else { mana = std::max(0, mana + manaChange); } } sendStats(); } void Player::changeSoul(int32_t soulChange) { if (soulChange > 0) { soul += std::min(soulChange, vocation->getSoulMax() - soul); } else { soul = std::max(0, soul + soulChange); } sendStats(); } bool Player::canWear(uint32_t lookType, uint8_t addons) const { if (group->access) { return true; } const Outfit* outfit = Outfits::getInstance().getOutfitByLookType(sex, lookType); if (!outfit) { return false; } if (outfit->premium && !isPremium()) { return false; } if (outfit->unlocked && addons == 0) { return true; } for (const OutfitEntry& outfitEntry : outfits) { if (outfitEntry.lookType != lookType) { continue; } return (outfitEntry.addons & addons) == addons; } return false; } bool Player::canLogout() { if (isConnecting) { return false; } if (getTile()->hasFlag(TILESTATE_NOLOGOUT)) { return false; } if (getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) { return true; } return !isPzLocked() && !hasCondition(CONDITION_INFIGHT); } void Player::genReservedStorageRange() { //generate outfits range uint32_t base_key = PSTRG_OUTFITS_RANGE_START; for (const OutfitEntry& entry : outfits) { storageMap[++base_key] = (entry.lookType << 16) | entry.addons; } } void Player::addOutfit(uint16_t lookType, uint8_t addons) { for (OutfitEntry& outfitEntry : outfits) { if (outfitEntry.lookType == lookType) { outfitEntry.addons |= addons; return; } } outfits.emplace_back(lookType, addons); } bool Player::removeOutfit(uint16_t lookType) { for (auto it = outfits.begin(), end = outfits.end(); it != end; ++it) { OutfitEntry& entry = *it; if (entry.lookType == lookType) { outfits.erase(it); return true; } } return false; } bool Player::removeOutfitAddon(uint16_t lookType, uint8_t addons) { for (OutfitEntry& outfitEntry : outfits) { if (outfitEntry.lookType == lookType) { outfitEntry.addons &= ~addons; return true; } } return false; } bool Player::getOutfitAddons(const Outfit& outfit, uint8_t& addons) const { if (group->access) { addons = 3; return true; } if (outfit.premium && !isPremium()) { return false; } for (const OutfitEntry& outfitEntry : outfits) { if (outfitEntry.lookType != outfit.lookType) { continue; } addons = outfitEntry.addons; return true; } if (!outfit.unlocked) { return false; } addons = 0; return true; } void Player::setSex(PlayerSex_t newSex) { sex = newSex; } Skulls_t Player::getSkull() const { if (hasFlag(PlayerFlag_NotGainInFight)) { return SKULL_NONE; } return skull; } Skulls_t Player::getSkullClient(const Creature* creature) const { if (!creature || g_game.getWorldType() != WORLD_TYPE_PVP) { return SKULL_NONE; } const Player* player = creature->getPlayer(); if (!player || player->getSkull() != SKULL_NONE) { return Creature::getSkullClient(creature); } if (isInWar(player)) { return SKULL_GREEN; } if (player->hasAttacked(this)) { return SKULL_YELLOW; } if (isPartner(player)) { return SKULL_GREEN; } return Creature::getSkullClient(creature); } bool Player::hasAttacked(const Player* attacked) const { if (hasFlag(PlayerFlag_NotGainInFight) || !attacked) { return false; } return attackedSet.find(attacked->id) != attackedSet.end(); } void Player::addAttacked(const Player* attacked) { if (hasFlag(PlayerFlag_NotGainInFight) || !attacked || attacked == this) { return; } attackedSet.insert(attacked->id); } void Player::removeAttacked(const Player* attacked) { if (!attacked || attacked == this) { return; } auto it = attackedSet.find(attacked->guid); if (it != attackedSet.end()) { attackedSet.erase(it); } } void Player::clearAttacked() { attackedSet.clear(); } void Player::addUnjustifiedDead(const Player* attacked) { if (hasFlag(PlayerFlag_NotGainInFight) || attacked == this || g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { return; } // current unjustified kill! murderTimeStamps.push_back(std::time(nullptr)); sendTextMessage(MESSAGE_STATUS_WARNING, "Warning! The murder of " + attacked->getName() + " was not justified."); if (playerKillerEnd == 0) { // white skull time, it only sets on first kill! playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::WHITE_SKULL_TIME); } uint32_t murderResult = checkPlayerKilling(); if (murderResult >= 1) { // red skull player playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::RED_SKULL_TIME); setSkull(SKULL_RED); if (murderResult == 2) { // banishment for too many unjustified kills Database* db = Database::getInstance(); std::ostringstream ss; ss << "INSERT INTO `account_bans` (`account_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES ("; ss << getAccount() << ", "; ss << db->escapeString("Too many unjustified kills") << ", "; ss << std::time(nullptr) << ", "; ss << std::time(nullptr) + g_config.getNumber(ConfigManager::BAN_LENGTH) << ", "; ss << "1);"; db->executeQuery(ss.str()); g_game.addMagicEffect(getPosition(), CONST_ME_GREEN_RINGS); g_game.removeCreature(this); disconnect(); } } } void Player::checkSkullTicks() { time_t today = std::time(nullptr); if (!hasCondition(CONDITION_INFIGHT) && ((skull == SKULL_RED && today >= playerKillerEnd) || (skull == SKULL_WHITE))) { setSkull(SKULL_NONE); } } bool Player::isPromoted() const { uint16_t promotedVocation = g_vocations.getPromotedVocation(vocation->getId()); return promotedVocation == VOCATION_NONE && vocation->getId() != promotedVocation; } double Player::getLostPercent() const { int32_t blessingCount = std::bitset<5>(blessings).count(); int32_t deathLosePercent = g_config.getNumber(ConfigManager::DEATH_LOSE_PERCENT); if (deathLosePercent != -1) { if (isPromoted()) { deathLosePercent -= 3; } deathLosePercent -= blessingCount; return std::max(0, deathLosePercent) / 100.; } double lossPercent; if (level >= 25) { double tmpLevel = level + (levelPercent / 100.); lossPercent = static_cast((tmpLevel + 50) * 50 * ((tmpLevel * tmpLevel) - (5 * tmpLevel) + 8)) / experience; } else { lossPercent = 10; } if (isPromoted()) { lossPercent *= 0.7; } return lossPercent * pow(0.92, blessingCount) / 100; } void Player::learnInstantSpell(const std::string& spellName) { if (!hasLearnedInstantSpell(spellName)) { learnedInstantSpellList.push_front(spellName); } } void Player::forgetInstantSpell(const std::string& spellName) { learnedInstantSpellList.remove(spellName); } bool Player::hasLearnedInstantSpell(const std::string& spellName) const { if (hasFlag(PlayerFlag_CannotUseSpells)) { return false; } if (hasFlag(PlayerFlag_IgnoreSpellCheck)) { return true; } for (const auto& learnedSpellName : learnedInstantSpellList) { if (strcasecmp(learnedSpellName.c_str(), spellName.c_str()) == 0) { return true; } } return false; } uint32_t Player::getWarId(const Player* targetPlayer) const { if (!targetPlayer || !guild) { return false; } const Guild* targetPlayerGuild = targetPlayer->getGuild(); if (!targetPlayerGuild) { return false; } const auto targetGuild = guild->getId(); const auto killerGuild = targetPlayerGuild->getId(); Database* db = Database::getInstance(); std::ostringstream query; query << "SELECT `id` FROM `guild_wars` WHERE `status` IN (1, 4) AND ((`guild1` = " << killerGuild << " AND `guild2` = " << targetGuild << ") OR (`guild1` = " << targetGuild << " AND `guild2` = " << killerGuild << "))"; DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return 0; } return result->getNumber("id"); } bool Player::isInWar(const Player* player) const { if (!player || !guild) { return false; } const Guild* playerGuild = player->getGuild(); if (!playerGuild) { return false; } return isInWarList(playerGuild->getId()) && player->isInWarList(guild->getId()); } bool Player::isInWarList(uint32_t guildId) const { return std::find(guildWarList.begin(), guildWarList.end(), guildId) != guildWarList.end(); } bool Player::isPremium() const { if (g_config.getBoolean(ConfigManager::FREE_PREMIUM) || hasFlag(PlayerFlag_IsAlwaysPremium)) { return true; } return premiumDays > 0; } void Player::setPremiumDays(int32_t v) { premiumDays = v; } PartyShields_t Player::getPartyShield(const Player* player) const { if (!player) { return SHIELD_NONE; } if (party) { if (party->getLeader() == player) { if (party->isSharedExperienceActive()) { if (party->isSharedExperienceEnabled()) { return SHIELD_YELLOW_SHAREDEXP; } if (party->canUseSharedExperience(player)) { return SHIELD_YELLOW_NOSHAREDEXP; } return SHIELD_YELLOW_NOSHAREDEXP_BLINK; } return SHIELD_YELLOW; } if (player->party == party) { if (party->isSharedExperienceActive()) { if (party->isSharedExperienceEnabled()) { return SHIELD_BLUE_SHAREDEXP; } if (party->canUseSharedExperience(player)) { return SHIELD_BLUE_NOSHAREDEXP; } return SHIELD_BLUE_NOSHAREDEXP_BLINK; } return SHIELD_BLUE; } if (isInviting(player)) { return SHIELD_WHITEBLUE; } } if (player->isInviting(this)) { return SHIELD_WHITEYELLOW; } return SHIELD_NONE; } bool Player::isInviting(const Player* player) const { if (!player || !party || party->getLeader() != this) { return false; } return party->isPlayerInvited(player); } bool Player::isPartner(const Player* player) const { if (!player || !party || player == this) { return false; } return party == player->party; } bool Player::isGuildMate(const Player* player) const { if (!player || !guild) { return false; } return guild == player->guild; } void Player::sendPlayerPartyIcons(Player* player) { sendCreatureShield(player); sendCreatureSkull(player); } bool Player::addPartyInvitation(Party* party) { auto it = std::find(invitePartyList.begin(), invitePartyList.end(), party); if (it != invitePartyList.end()) { return false; } invitePartyList.push_front(party); return true; } void Player::removePartyInvitation(Party* party) { invitePartyList.remove(party); } void Player::clearPartyInvitations() { for (Party* invitingParty : invitePartyList) { invitingParty->removeInvite(*this, false); } invitePartyList.clear(); } void Player::sendClosePrivate(uint16_t channelId) { if (channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY) { g_chat->removeUserFromChannel(*this, channelId); } if (client) { client->sendClosePrivate(channelId); } } bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) { if (tries == 0 || skill == SKILL_LEVEL) { return false; } bool sendUpdate = false; uint32_t oldSkillValue, newSkillValue; long double oldPercentToNextLevel, newPercentToNextLevel; if (skill == SKILL_MAGLEVEL) { uint64_t currReqMana = vocation->getReqMana(magLevel); uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); if (currReqMana >= nextReqMana) { return false; } oldSkillValue = magLevel; oldPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; g_events->eventPlayerOnGainSkillTries(this, SKILL_MAGLEVEL, tries); uint32_t currMagLevel = magLevel; while ((manaSpent + tries) >= nextReqMana) { tries -= nextReqMana - manaSpent; magLevel++; manaSpent = 0; g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel); sendUpdate = true; currReqMana = nextReqMana; nextReqMana = vocation->getReqMana(magLevel + 1); if (currReqMana >= nextReqMana) { tries = 0; break; } } manaSpent += tries; if (magLevel != currMagLevel) { std::ostringstream ss; ss << "You advanced to magic level " << magLevel << '.'; sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); } uint8_t newPercent; if (nextReqMana > currReqMana) { newPercent = Player::getPercentLevel(manaSpent, nextReqMana); newPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; } else { newPercent = 0; newPercentToNextLevel = 0; } if (newPercent != magLevelPercent) { magLevelPercent = newPercent; sendUpdate = true; } newSkillValue = magLevel; } else { uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level); uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); if (currReqTries >= nextReqTries) { return false; } oldSkillValue = skills[skill].level; oldPercentToNextLevel = static_cast(skills[skill].tries * 100) / nextReqTries; g_events->eventPlayerOnGainSkillTries(this, skill, tries); uint32_t currSkillLevel = skills[skill].level; while ((skills[skill].tries + tries) >= nextReqTries) { tries -= nextReqTries - skills[skill].tries; skills[skill].level++; skills[skill].tries = 0; skills[skill].percent = 0; g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level); sendUpdate = true; currReqTries = nextReqTries; nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); if (currReqTries >= nextReqTries) { tries = 0; break; } } skills[skill].tries += tries; if (currSkillLevel != skills[skill].level) { std::ostringstream ss; ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill].level << '.'; sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); } uint8_t newPercent; if (nextReqTries > currReqTries) { newPercent = Player::getPercentLevel(skills[skill].tries, nextReqTries); newPercentToNextLevel = static_cast(skills[skill].tries * 100) / nextReqTries; } else { newPercent = 0; newPercentToNextLevel = 0; } if (skills[skill].percent != newPercent) { skills[skill].percent = newPercent; sendUpdate = true; } newSkillValue = skills[skill].level; } if (sendUpdate) { sendSkills(); } std::ostringstream ss; ss << std::fixed << std::setprecision(2) << "Your " << ucwords(getSkillName(skill)) << " skill changed from level " << oldSkillValue << " (with " << oldPercentToNextLevel << "% progress towards level " << (oldSkillValue + 1) << ") to level " << newSkillValue << " (with " << newPercentToNextLevel << "% progress towards level " << (newSkillValue + 1) << ')'; sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); return sendUpdate; } uint64_t Player::getMoney() const { std::vector containers; uint64_t moneyCount = 0; for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { Item* item = inventory[i]; if (!item) { continue; } const Container* container = item->getContainer(); if (container) { containers.push_back(container); } else { moneyCount += item->getWorth(); } } size_t i = 0; while (i < containers.size()) { const Container* container = containers[i++]; for (const Item* item : container->getItemList()) { const Container* tmpContainer = item->getContainer(); if (tmpContainer) { containers.push_back(tmpContainer); } else { moneyCount += item->getWorth(); } } } return moneyCount; } size_t Player::getMaxVIPEntries() const { if (group->maxVipEntries != 0) { return group->maxVipEntries; } else if (isPremium()) { return 100; } return 20; } size_t Player::getMaxDepotItems() const { if (group->maxDepotItems != 0) { return group->maxDepotItems; } else if (isPremium()) { return 2000; } return 1000; } std::forward_list Player::getMuteConditions() const { std::forward_list muteConditions; for (Condition* condition : conditions) { if (condition->getTicks() <= 0) { continue; } ConditionType_t type = condition->getType(); if (type != CONDITION_MUTED && type != CONDITION_CHANNELMUTEDTICKS && type != CONDITION_YELLTICKS) { continue; } muteConditions.push_front(condition); } return muteConditions; } void Player::setGuild(Guild* guild) { if (guild == this->guild) { return; } Guild* oldGuild = this->guild; this->guildNick.clear(); this->guild = nullptr; this->guildRank = nullptr; if (guild) { const GuildRank* rank = guild->getRankByLevel(1); if (!rank) { return; } this->guild = guild; this->guildRank = rank; guild->addMember(this); } if (oldGuild) { oldGuild->removeMember(this); } }