Ir para conteúdo
  • Cadastre-se

Posts Recomendados

Seguinte galera, eu estava utilizando uma base otx2 e achei um script bem bacana onde voce vai na source e edita a linha uint32_t Player::getAttackSpeed() const e com isso você consegue colocar o fist attack para influenciar no attack speed. porem agora estou mudando de source para a base do tfs 1.3 Downgrade para 8.6 porem não estou achando onde editar para que o fist influencie no attack speed.

 

 

 

Aqui vai o Player.cpp

 

Spoiler

/**
 * The Forgotten Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2019 Mark Samman <[email protected]>
 *
 * 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 <bitset>

#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"
#include "weapons.h"

extern ConfigManager g_config;
extern Game g_game;
extern Chat* g_chat;
extern Vocations g_vocations;
extern MoveEvents* g_moveEvents;
extern Weapons* g_weapons;
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 (party) {
        if (lookDistance == -1) {
            s << " Your party has ";
        } else if (sex == PLAYERSEX_FEMALE) {
            s << " She is in a party with ";
        } else {
            s << " He is in a party with ";
        }

        size_t memberCount = party->getMemberCount() + 1;
        if (memberCount == 1) {
            s << "1 member and ";
        } else {
            s << memberCount << " members and ";
        }

        size_t invitationCount = party->getInvitationCount();
        if (invitationCount == 1) {
            s << "1 pending invitation.";
        } else {
            s << invitationCount << " pending invitations.";
        }
    }

    if (!guild || !guildRank) {
        return s.str();
    }

    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 << ')';
    }

    size_t memberCount = guild->getMemberCount();
    if (memberCount == 1) {
        s << ", which has 1 member, " << guild->getMembersOnline().size() << " of them online.";
    } else {
        s << ", which has " << memberCount << " members, " << guild->getMembersOnline().size() << " of them online.";
    }
    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(slots_t slot, bool ignoreAmmo) const
{
    Item* item = inventory[slot];
    if (!item) {
        return nullptr;
    }

    WeaponType_t weaponType = item->getWeaponType();
    if (weaponType == WEAPON_NONE || weaponType == WEAPON_SHIELD || weaponType == WEAPON_AMMO) {
        return nullptr;
    }

    if (!ignoreAmmo && weaponType == WEAPON_DISTANCE) {
        const ItemType& it = Item::items[item->getID()];
        if (it.ammoType != AMMO_NONE) {
            Item* ammoItem = inventory[CONST_SLOT_AMMO];
            if (!ammoItem || ammoItem->getAmmoType() != it.ammoType) {
                return nullptr;
            }
            item = ammoItem;
        }
    }
    return item;
}

Item* Player::getWeapon(bool ignoreAmmo/* = false*/) const
{
    Item* item = getWeapon(CONST_SLOT_LEFT, ignoreAmmo);
    if (item) {
        return item;
    }

    item = getWeapon(CONST_SLOT_RIGHT, ignoreAmmo);
    if (item) {
        return item;
    }
    return nullptr;
}

WeaponType_t Player::getWeaponType() const
{
    Item* item = getWeapon();
    if (!item) {
        return WEAPON_NONE;
    }
    return item->getWeaponType();
}

int32_t Player::getWeaponSkill(const Item* item) const
{
    if (!item) {
        return getSkillLevel(SKILL_FIST);
    }

    int32_t attackSkill;

    WeaponType_t weaponType = item->getWeaponType();
    switch (weaponType) {
        case WEAPON_SWORD: {
            attackSkill = getSkillLevel(SKILL_SWORD);
            break;
        }

        case WEAPON_CLUB: {
            attackSkill = getSkillLevel(SKILL_CLUB);
            break;
        }

        case WEAPON_AXE: {
            attackSkill = getSkillLevel(SKILL_AXE);
            break;
        }

        case WEAPON_DISTANCE: {
            attackSkill = getSkillLevel(SKILL_DISTANCE);
            break;
        }

        default: {
            attackSkill = 0;
            break;
        }
    }
    return attackSkill;
}

int32_t Player::getArmor() const
{
    int32_t armor = 0;

    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();
        }
    }
    return static_cast<int32_t>(armor * vocation->armorMultiplier);
}

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() const
{
    int32_t defenseSkill = getSkillLevel(SKILL_FIST);
    int32_t defenseValue = 7;
    const Item* weapon;
    const Item* shield;
    getShieldAndWeapon(shield, weapon);

    if (weapon) {
        defenseValue = weapon->getDefense() + weapon->getExtraDefense();
        defenseSkill = getWeaponSkill(weapon);
    }

    if (shield) {
        defenseValue = weapon != nullptr ? shield->getDefense() + weapon->getExtraDefense() : shield->getDefense();
        defenseSkill = getSkillLevel(SKILL_SHIELD);
    }

    if (defenseSkill == 0) {
        switch (fightMode) {
            case FIGHTMODE_ATTACK:
            case FIGHTMODE_BALANCED:
                return 1;

            case FIGHTMODE_DEFENSE:
                return 2;
        }
    }

    return (defenseSkill / 4. + 2.23) * defenseValue * 0.15 * getDefenseFactor() * vocation->defenseMultiplier;
}

float Player::getAttackFactor() const
{
    switch (fightMode) {
        case FIGHTMODE_ATTACK: return 1.0f;
        case FIGHTMODE_BALANCED: return 1.2f;
        case FIGHTMODE_DEFENSE: return 2.0f;
        default: return 1.0f;
    }
}

float Player::getDefenseFactor() const
{
    switch (fightMode) {
        case FIGHTMODE_ATTACK: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.5f : 1.0f;
        case FIGHTMODE_BALANCED: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.75f : 1.0f;
        case FIGHTMODE_DEFENSE: return 1.0f;
        default: return 1.0f;
    }
}

uint16_t Player::getClientIcons() const
{
    uint16_t icons = 0;
    for (Condition* condition : conditions) {
        if (!isSuppress(condition->getType())) {
            icons |= condition->getIcons();
        }
    }

    if (pzLocked) {
        icons |= ICON_REDSWORDS;
    }

    if (tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
        icons |= ICON_PIGEON;

        // Don't show ICON_SWORDS if player is in protection zone.
        if (hasBitSet(ICON_SWORDS, icons)) {
            icons &= ~ICON_SWORDS;
        }
    }

    // Game client debugs with 10 or more icons
    // so let's prevent that from happening.
    std::bitset<20> icon_bitset(static_cast<uint64_t>(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) {
            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 = -1;
        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;
}

bool Player::canWalkthrough(const Creature* creature) const
{
    if (group->access || creature->isInGhostMode()) {
        return true;
    }

    const Player* player = creature->getPlayer();
    if (!player) {
        return false;
    }

    const Tile* playerTile = player->getTile();
    if (!playerTile || (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && player->getLevel() > static_cast<uint32_t>(g_config.getNumber(ConfigManager::PROTECTION_LEVEL)))) {
        return false;
    }

    const Item* playerTileGround = playerTile->getGround();
    if (!playerTileGround || !playerTileGround->hasWalkStack()) {
        return false;
    }

    Player* thisPlayer = const_cast<Player*>(this);
    if ((OTSYS_TIME() - lastWalkthroughAttempt) > 2000) {
        thisPlayer->setLastWalkthroughAttempt(OTSYS_TIME());
        return false;
    }

    if (creature->getPosition() != lastWalkthroughPosition) {
        thisPlayer->setLastWalkthroughPosition(creature->getPosition());
        return false;
    }

    thisPlayer->setLastWalkthroughPosition(creature->getPosition());
    return true;
}

bool Player::canWalkthroughEx(const Creature* creature) const
{
    if (group->access) {
        return true;
    }

    const Player* player = creature->getPlayer();
    if (!player) {
        return false;
    }

    const Tile* playerTile = player->getTile();
    return playerTile && (playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || player->getLevel() <= static_cast<uint32_t>(g_config.getNumber(ConfigManager::PROTECTION_LEVEL)));
}

void Player::onReceiveMail() const
{
    if (isNearDepotBox()) {
        sendTextMessage(MESSAGE_EVENT_ADVANCE, "New mail has arrived.");
    }
}

bool Player::isNearDepotBox() 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 (tile->hasFlag(TILESTATE_DEPOT)) {
                return true;
            }
        }
    }
    return false;
}

DepotChest* Player::getDepotChest(uint32_t depotId, bool autoCreate)
{
    auto it = depotChests.find(depotId);
    if (it != depotChests.end()) {
        return it->second;
    }

    if (!autoCreate) {
        return nullptr;
    }

    DepotChest* depotChest = new DepotChest(ITEM_DEPOT);
    depotChest->incrementReferenceCounter();
    depotChests[depotId] = depotChest;
    return depotChest;
}

DepotLocker* Player::getDepotLocker(uint32_t depotId)
{
    auto it = depotLockerMap.find(depotId);
    if (it != depotLockerMap.end()) {
        return it->second;
    }

    DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER);
    depotLocker->setDepotId(depotId);
    depotLocker->internalAddThing(getDepotChest(depotId, true));
    depotLockerMap[depotId] = depotLocker;
    return depotLocker;
}

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<uint16_t>(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<slots_t>(slot), false);
            }
        }

        for (Condition* condition : storedConditionList) {
            addCondition(condition);
        }
        storedConditionList.clear();

        BedItem* bed = g_game.getBedBySleeper(guid);
        if (bed) {
            bed->wakeUp(this);
        }

        Account account = IOLoginData::loadAccount(accountNumber);
        Game::updatePremium(account);

        std::cout << name << " has logged in." << std::endl;

        if (guild) {
            guild->addMember(this);
        }

        for (Condition* condition : getMuteConditions()) {
            condition->setTicks(condition->getTicks());
            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);
        }
    }

    g_game.updateCreatureWalkthrough(this);
    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);
        }

        closeShopWindow();

        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::openShopWindow(const std::list<ShopInfo>& shop)
{
    shopItemList = shop;
    sendShop();
    sendSaleItemList();
}

bool Player::closeShopWindow(bool sendCloseShopWindow /*= true*/)
{
    //unreference callbacks
    int32_t onBuy;
    int32_t onSell;

    Npc* npc = getShopOwner(onBuy, onSell);
    if (!npc) {
        shopItemList.clear();
        return false;
    }

    setShopOwner(nullptr, -1, -1);
    npc->onPlayerEndTrade(this, onBuy, onSell);

    if (sendCloseShopWindow) {
        sendCloseShop();
    }

    shopItemList.clear();
    return true;
}

void Player::onWalk(Direction& dir)
{
    Creature::onWalk(dir);
    setNextActionTask(nullptr);
    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 = dynamic_cast<const Container*>(container->getParent()) != nullptr;
    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<const Container*>(item->getParent());
        while (container) {
            if (container == tradeItem) {
                g_game.internalCloseTrade(this);
                break;
            }

            container = dynamic_cast<const Container*>(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<int64_t>(SCHEDULER_MINTICKS, nextAction - OTSYS_TIME());
}

void Player::onThink(uint32_t interval)
{
    Creature::onThink(interval);

    sendPing();

    MessageBufferTicks += interval;
    if (MessageBufferTicks >= 1500) {
        MessageBufferTicks = 0;
        addMessageBuffer();
    }

    if (!getTile()->hasFlag(TILESTATE_NOLOGOUT) && !isAccessPlayer()) {
        idleTime += interval;
        const int32_t kickAfterMinutes = g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES);
        if (idleTime > (kickAfterMinutes * 60000) + 60000) {
            kickPlayer(true);
        } else if (client && idleTime == 60000 * kickAfterMinutes) {
            std::ostringstream ss;
            ss << "There was no variation in your behaviour for " << kickAfterMinutes << " minutes. You will be disconnected in one minute if there is no change in your actions until then.";
            client->sendTextMessage(TextMessage(MESSAGE_STATUS_WARNING, ss.str()));
        }
    }

    if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) {
        checkSkullTicks(interval / 1000);
    }
}

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<uint32_t>(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) {
        std::string expString = std::to_string(exp) + (exp != 1 ? " experience points." : " experience point.");

        TextMessage message(MESSAGE_STATUS_DEFAULT, "You gained " + expString);
        sendTextMessage(message);

        std::ostringstream strExp;
        strExp << exp;
        g_game.addAnimatedText(strExp.str(), position, TEXTCOLOR_WHITE);

        SpectatorVec spectators;
        g_game.map.getSpectators(spectators, position, false, true);
        spectators.erase(this);
        if (!spectators.empty()) {
            message.type = MESSAGE_STATUS_DEFAULT;
            message.text = getName() + " gained " + expString;
            for (Creature* spectator : spectators) {
                spectator->getPlayer()->sendTextMessage(message);
            }
        }
    }

    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) {
        health = getMaxHealth();
        mana = getMaxMana();

        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, bool sendText/* = false*/)
{
    if (experience == 0 || exp == 0) {
        return;
    }

    g_events->eventPlayerOnLoseExperience(this, exp);
    if (exp == 0) {
        return;
    }

    uint64_t lostExp = experience;
    experience = std::max<int64_t>(0, experience - exp);

    if (sendText) {
        lostExp -= experience;

        std::string expString = std::to_string(lostExp) + (lostExp != 1 ? " experience points." : " experience point.");

        TextMessage message(MESSAGE_STATUS_DEFAULT, "You lost " + expString);
        sendTextMessage(message);

        std::ostringstream strExp;
        strExp << lostExp;
        g_game.addAnimatedText(strExp.str(), position, TEXTCOLOR_RED);

        SpectatorVec spectators;
        g_game.map.getSpectators(spectators, position, false, true);
        spectators.erase(this);
        if (!spectators.empty()) {
            message.type = MESSAGE_STATUS_DEFAULT;
            message.text = getName() + " lost " + expString;
            for (Creature* spectator : spectators) {
                spectator->getPlayer()->sendTextMessage(message);
            }
        }
    }

    uint32_t oldLevel = level;
    uint64_t currLevelExp = Player::getExpForLevel(level);

    while (level > 1 && experience < currLevelExp) {
        --level;
        healthMax = std::max<int32_t>(0, healthMax - vocation->getHPGain());
        manaMax = std::max<int32_t>(0, manaMax - vocation->getManaGain());
        capacity = std::max<int32_t>(0, capacity - vocation->getCapGain());
        currLevelExp = Player::getExpForLevel(level);
    }

    if (oldLevel != level) {
        health = getMaxHealth();
        mana = getMaxMana();

        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;
}

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) {
        damage = 0;
        return BLOCK_ARMOR;
    }

    for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
        if (!isItemAbilityEnabled(static_cast<slots_t>(slot))) {
            continue;
        }

        Item* item = inventory[slot];
        if (!item) {
            continue;
        }

        const ItemType& it = Item::items[item->getID()];
        if (!it.abilities) {
            if (damage <= 0) {
                damage = 0;
                return BLOCK_ARMOR;
            }

            continue;
        }

        const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)];
        if (absorbPercent != 0) {
            damage -= std::round(damage * (absorbPercent / 100.));

            uint16_t charges = item->getCharges();
            if (charges != 0) {
                g_game.transformItem(item, item->getID(), charges - 1);
            }
        }

        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) {
                    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::death(Creature* lastHitCreature)
{
    loginPosition = town->getTemplePosition();

    if (skillLoss) {
        bool lastHitPlayer = Player::lastHitIsPlayer(lastHitCreature);

        //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<uint64_t>(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<uint32_t>(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<int32_t>(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<uint64_t>(experience * deathLossPercent);
        g_events->eventPlayerOnLoseExperience(this, expLoss);

        if (expLoss != 0) {
            uint32_t oldLevel = level;

            if (vocation->getId() == VOCATION_NONE || level > 7) {
                experience -= expLoss;
            }

            while (level > 1 && experience < Player::getExpForLevel(level)) {
                --level;
                healthMax = std::max<int32_t>(0, healthMax - vocation->getHPGain());
                manaMax = std::max<int32_t>(0, manaMax - vocation->getManaGain());
                capacity = std::max<int32_t>(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 (lastHitPlayer) {
                bitset.reset(5);
                blessings = bitset.to_ulong();
            } else {
                blessings = 32;
            }
        } else {
            blessings = 0;
        }

        sendStats();
        sendSkills();
        sendReLoginWindow();

        if (getSkull() == SKULL_BLACK) {
            health = 40;
            mana = 0;
        } else {
            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;
            }
        }
    } 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::addCombatExhaust(uint32_t ticks)
{
    Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST_COMBAT, ticks, 0);
    addCondition(condition);
}

void Player::addHealExhaust(uint32_t ticks)
{
    Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST_HEAL, ticks, 0);
    addCondition(condition);
}

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);
}

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 (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 200) { // max number of buddies is 200 in 9.53
        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 (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 200) { // max number of buddies is 200 in 9.53
        return false;
    }

    return VIPList.insert(vipGuid).second;
}

//close container and its child containers
void Player::autoCloseContainers(const Container* container)
{
    std::vector<uint32_t> 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<Container*>(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)) {
        if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) {
            ret = RETURNVALUE_CANNOTBEDRESSED;
        } else {
            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 (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) {
                    if (item->getWeaponType() != WEAPON_SHIELD) {
                        ret = RETURNVALUE_CANNOTBEDRESSED;
                    } else {
                        const Item* leftItem = inventory[CONST_SLOT_LEFT];
                        if (leftItem) {
                            if ((leftItem->getSlotPosition() | slotPosition) & SLOTP_TWO_HAND) {
                                ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE;
                            } else {
                                ret = RETURNVALUE_NOERROR;
                            }
                        } else {
                            ret = RETURNVALUE_NOERROR;
                        }
                    }
                } else 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 (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) {
                    WeaponType_t type = item->getWeaponType();
                    if (type == WEAPON_NONE || type == WEAPON_SHIELD) {
                        ret = RETURNVALUE_CANNOTBEDRESSED;
                    } else if (inventory[CONST_SLOT_RIGHT] && (slotPosition & SLOTP_TWO_HAND)) {
                        ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE;
                    } else {
                        ret = RETURNVALUE_NOERROR;
                    }
                } else 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: {
            if ((slotPosition & SLOTP_AMMO) || g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) {
                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) {
        return ret;
    }

    //check if enough capacity
    if (!hasCapacity(item, count)) {
        return RETURNVALUE_NOTENOUGHCAPACITY;
    }

    ret = g_moveEvents->onPlayerEquip(const_cast<Player*>(this), const_cast<Item*>(item), static_cast<slots_t>(index), true);
    if (ret != RETURNVALUE_NOERROR) {
        return ret;
    }

    //need an exchange with source? (destination item is swapped with currently moved item)
    const Item* inventoryItem = getInventoryItem(static_cast<slots_t>(index));
    if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->getID() != item->getID())) {
        const Cylinder* cylinder = item->getTopParent();
        if (cylinder && (dynamic_cast<const DepotChest*>(cylinder) || dynamic_cast<const Player*>(cylinder))) {
            return RETURNVALUE_NEEDEXCHANGE;
        }

        return RETURNVALUE_NOTENOUGHROOM;
    }
    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<Container*> 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--;
                }

                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 (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<Cylinder*>(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<slots_t>(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<slots_t>(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<slots_t>(index));
    if (!oldItem) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    Item* item = thing->getItem();
    if (!item) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    //send to client
    sendInventoryItem(static_cast<slots_t>(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<slots_t>(index), nullptr);

            //event methods
            onRemoveInventoryItem(item);

            item->setParent(nullptr);
            inventory[index] = nullptr;
        } else {
            uint8_t newCount = static_cast<uint8_t>(std::max<int32_t>(0, item->getItemCount() - count));
            item->setItemCount(newCount);

            //send change to client
            sendInventoryItem(static_cast<slots_t>(index), item);

            //event methods
            onUpdateInventoryItem(item, item);
        }
    } else {
        //send change to client
        sendInventoryItem(static_cast<slots_t>(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;
}

bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped/* = false*/) const
{
    if (amount == 0) {
        return true;
    }

    std::vector<Item*> 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()) {
            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<uint32_t, uint32_t>& Player::getAllItemTypeCount(std::map<uint32_t, uint32_t>& 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* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/)
{
    if (link == LINK_OWNER) {
        //calling movement scripts
        g_moveEvents->onPlayerEquip(this, thing->getItem(), static_cast<slots_t>(index), false);
    }

    bool requireListUpdate = false;

    if (link == LINK_OWNER || link == LINK_TOPPARENT) {
        const Item* i = (oldParent ? oldParent->getItem() : nullptr);

        // Check if we owned the old container too, so we don't need to do anything,
        // as the list was updated in postRemoveNotification
        assert(i ? i->getContainer() != nullptr : true);

        if (i) {
            requireListUpdate = i->getContainer()->getHoldingPlayer() != this;
        } else {
            requireListUpdate = oldParent != this;
        }

        updateInventoryWeight();
        updateItemsLight();
        sendStats();
    }

    if (const Item* item = thing->getItem()) {
        if (const Container* container = item->getContainer()) {
            onSendContainer(container);
        }

        if (shopOwner && requireListUpdate) {
            updateSaleShopList(item);
        }
    } else if (const Creature* creature = thing->getCreature()) {
        if (creature == this) {
            //check containers
            std::vector<Container*> 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* newParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/)
{
    if (link == LINK_OWNER) {
        //calling movement scripts
        g_moveEvents->onPlayerDeEquip(this, thing->getItem(), static_cast<slots_t>(index));
    }

    bool requireListUpdate = false;

    if (link == LINK_OWNER || link == LINK_TOPPARENT) {
        const Item* i = (newParent ? newParent->getItem() : nullptr);

        // Check if we owned the old container too, so we don't need to do anything,
        // as the list was updated in postRemoveNotification
        assert(i ? i->getContainer() != nullptr : true);

        if (i) {
            requireListUpdate = i->getContainer()->getHoldingPlayer() != this;
        } else {
            requireListUpdate = newParent != this;
        }

        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<const Container*>(container->getTopParent())) {
                if (const DepotChest* depotChest = dynamic_cast<const DepotChest*>(topContainer)) {
                    bool isOwner = false;

                    for (const auto& it : depotChests) {
                        if (it.second == depotChest) {
                            isOwner = true;
                            onSendContainer(container);
                        }
                    }

                    if (!isOwner) {
                        autoCloseContainers(container);
                    }
                } else {
                    onSendContainer(container);
                }
            } else {
                autoCloseContainers(container);
            }
        }

        if (shopOwner && requireListUpdate) {
            updateSaleShopList(item);
        }
    }
}

bool Player::updateSaleShopList(const Item* item)
{
    uint16_t itemId = item->getID();
    if (itemId != ITEM_GOLD_COIN && itemId != ITEM_PLATINUM_COIN && itemId != ITEM_CRYSTAL_COIN) {
        auto it = std::find_if(shopItemList.begin(), shopItemList.end(), [itemId](const ShopInfo& shopInfo) { return shopInfo.itemId == itemId && shopInfo.sellPrice != 0; });
        if (it == shopItemList.end()) {
            const Container* container = item->getContainer();
            if (!container) {
                return false;
            }

            const auto& items = container->getItemList();
            return std::any_of(items.begin(), items.end(), [this](const Item* containerItem) {
                return updateSaleShopList(containerItem);
            });
        }
    }

    if (client) {
        client->sendSaleItemList(shopItemList);
    }
    return true;
}

bool Player::hasShopItemForSale(uint32_t itemId, uint8_t subType) const
{
    const ItemType& itemType = Item::items[itemId];
    return std::any_of(shopItemList.begin(), shopItemList.end(), [&](const ShopInfo& shopInfo) {
        return shopInfo.itemId == itemId && shopInfo.buyPrice != 0 && (!itemType.isFluidContainer() || shopInfo.subType == subType);
    });
}

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);
    }
}

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 && 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()) {
        bool result = false;

        Item* tool = getWeapon();
        const Weapon* weapon = g_weapons->getWeapon(tool);
        uint32_t delay = getAttackSpeed();
        bool classicSpeed = g_config.getBoolean(ConfigManager::CLASSIC_ATTACK_SPEED);

        if (weapon) {
            if (!weapon->interruptSwing()) {
                result = weapon->useWeapon(this, tool, attackedCreature);
            } else if (!classicSpeed && !canDoAction()) {
                delay = getNextActionTime();
            } else {
                result = weapon->useWeapon(this, tool, attackedCreature);
            }
        } else {
            result = Weapon::useFist(this, attackedCreature);
        }

        SchedulerTask* task = createSchedulerTask(std::max<uint32_t>(SCHEDULER_MINTICKS, delay), std::bind(&Game::checkCreatureAttack, &g_game, getID()));
        if (!classicSpeed) {
            setNextActionTask(task);
        } else {
            g_scheduler.addEvent(task);
        }

        if (result) {
            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<int32_t>(attackerPlayer->getLevel() - level)) <= g_config.getNumber(ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)) {
            return std::max<uint64_t>(0, std::floor(getLostExperience() * getDamageRatio(attacker) * 0.75));
        }
    }
    return 0;
}

void Player::onFollowCreature(const Creature* creature)
{
    if (!creature) {
        stopWalk();
    }
}

void Player::setChaseMode(bool mode)
{
    bool prevChaseMode = chaseMode;
    chaseMode = mode;

    if (prevChaseMode != chaseMode) {
        if (chaseMode) {
            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;

    for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
        Item* item = inventory[i];
        if (item) {
            LightInfo curLight = item->getLightInfo();

            if (curLight.level > maxLight.level) {
                maxLight = std::move(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_DEFAULT, "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;

        case CONDITION_CURSED:
            sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are cursed.");
            break;

        case CONDITION_FREEZING:
            sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are freezing.");
            break;

        case CONDITION_DAZZLED:
            sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are dazzled.");
            break;

        case CONDITION_BLEEDING:
            sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are bleeding.");
            break;

        default:
            break;
    }
}

void Player::onEndCondition(ConditionType_t type)
{
    Creature::onEndCondition(type);

    if (type == CONDITION_INFIGHT) {
        onIdleStatus();
        pzLocked = false;
        clearAttacked();

        if (getSkull() != SKULL_RED && getSkull() != SKULL_BLACK) {
            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<slots_t>(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, bool addFightTicks /* = true */)
{
    Creature::onAttackedCreature(target);

    if (target->getZone() == ZONE_PVP) {
        return;
    }

    if (target == this) {
        if (addFightTicks) {
            addInFightTicks();
        }
        return;
    }

    if (hasFlag(PlayerFlag_NotGainInFight)) {
        return;
    }

    Player* targetPlayer = target->getPlayer();
    if (targetPlayer && !isPartner(targetPlayer) && !isGuildMate(targetPlayer)) {
        if (!pzLocked && g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) {
            pzLocked = true;
            sendIcons();
        }

        targetPlayer->addInFightTicks();

        if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) {
            addAttacked(targetPlayer);
            targetPlayer->sendCreatureSkull(this);
        } else if (!targetPlayer->hasAttacked(this)) {
            if (!pzLocked) {
                pzLocked = true;
                sendIcons();
            }

            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);
                }
            }
        }
    }

    if (addFightTicks) {
        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);

    Player* targetPlayer = target->getPlayer();
    if (!targetPlayer) {
        return false;
    }

    if (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) && !isGuildMate(targetPlayer) && 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), 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<int32_t>(manaChange, getMaxMana() - mana);
        } else {
            mana = std::max<int32_t>(0, mana + manaChange);
        }
    }

    sendStats();
}

void Player::changeSoul(int32_t soulChange)
{
    if (soulChange > 0) {
        soul += std::min<int32_t>(soulChange, vocation->getSoulMax() - soul);
    } else {
        soul = std::max<int32_t>(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->getGuildWarVector().empty() && guild == player->getGuild()) {
        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->guid) != attackedSet.end();
}

void Player::addAttacked(const Player* attacked)
{
    if (hasFlag(PlayerFlag_NotGainInFight) || !attacked || attacked == this) {
        return;
    }

    attackedSet.insert(attacked->guid);
}

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;
    }

    sendTextMessage(MESSAGE_EVENT_ADVANCE, "Warning! The murder of " + attacked->getName() + " was not justified.");

    skullTicks += g_config.getNumber(ConfigManager::FRAG_TIME);

    if (getSkull() != SKULL_BLACK) {
        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))) {
            setSkull(SKULL_BLACK);
        } 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))) {
            setSkull(SKULL_RED);
        }
    }
}

void Player::checkSkullTicks(int64_t ticks)
{
    int64_t newTicks = skullTicks - ticks;
    if (newTicks < 0) {
        skullTicks = 0;
    } else {
        skullTicks = newTicks;
    }

    if ((skull == SKULL_RED || skull == SKULL_BLACK) && skullTicks < 1 && !hasCondition(CONDITION_INFIGHT)) {
        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<int32_t>(0, deathLosePercent) / 100.;
    }

    double lossPercent;
    if (level >= 25) {
        double tmpLevel = level + (levelPercent / 100.);
        lossPercent = static_cast<double>((tmpLevel + 50) * 50 * ((tmpLevel * tmpLevel) - (5 * tmpLevel) + 8)) / experience;
    } else {
        lossPercent = 10;
    }

    double percentReduction = 0;
    if (isPromoted()) {
        percentReduction += 30;
    }
    percentReduction += blessingCount * 8;
    return lossPercent * (1 - (percentReduction / 100.)) / 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;
}

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(guildWarVector.begin(), guildWarVector.end(), guildId) != guildWarVector.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();
}

GuildEmblems_t Player::getGuildEmblem(const Player* player) const
{
    if (!player) {
        return GUILDEMBLEM_NONE;
    }

    const Guild* playerGuild = player->getGuild();
    if (!playerGuild || player->getGuildWarVector().empty()) {
        return GUILDEMBLEM_NONE;
    }

    if (guild == playerGuild) {
        return GUILDEMBLEM_ALLY;
    } else if (isInWar(player)) {
        return GUILDEMBLEM_ENEMY;
    }

    return GUILDEMBLEM_NEUTRAL;
}

uint16_t Player::getHelpers() const
{
    uint16_t helpers;

    if (guild && party) {
        std::unordered_set<Player*> helperSet;

        const auto& guildMembers = guild->getMembersOnline();
        helperSet.insert(guildMembers.begin(), guildMembers.end());

        const auto& partyMembers = party->getMembers();
        helperSet.insert(partyMembers.begin(), partyMembers.end());

        const auto& partyInvitees = party->getInvitees();
        helperSet.insert(partyInvitees.begin(), partyInvitees.end());

        helperSet.insert(party->getLeader());

        helpers = helperSet.size();
    } else if (guild) {
        helpers = guild->getMembersOnline().size();
    } else if (party) {
        helpers = party->getMemberCount() + party->getInvitationCount() + 1;
    } else {
        helpers = 0;
    }

    return helpers;
}

void Player::sendClosePrivate(uint16_t channelId)
{
    if (channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY) {
        g_chat->removeUserFromChannel(*this, channelId);
    }

    if (client) {
        client->sendClosePrivate(channelId);
    }
}

uint64_t Player::getMoney() const
{
    std::vector<const Container*> 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;
}

std::forward_list<Condition*> Player::getMuteConditions() const
{
    std::forward_list<Condition*> 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);
    }
}
 

 

 

Link para o post
Compartilhar em outros sites
  • 5 months later...

Participe da conversa

Você pode postar agora e se cadastrar mais tarde. Se você tem uma conta, faça o login para postar com sua conta.

Visitante
Responder

×   Você colou conteúdo com formatação.   Remover formatação

  Apenas 75 emojis são permitidos.

×   Seu link foi automaticamente incorporado.   Mostrar como link

×   Seu conteúdo anterior foi restaurado.   Limpar o editor

×   Não é possível colar imagens diretamente. Carregar ou inserir imagens do URL.

  • Quem Está Navegando   0 membros estão online

    Nenhum usuário registrado visualizando esta página.

  • Conteúdo Similar

    • Por LasseXeterno
      Então, estou tentando adicionar uma nova "race" no meu Ot de base Cyan, tentei seguir 3 tutoriais aqui do tibiaking, um sobre race, porém nos códigos do meu servidor não tem o constant.h e nem o monster.cpp. E o outro tutorial, eu fiz tudo que ele pediu e quando entrei no game para testar, funcionava os golpes e as imunidades, porém não aparecia o número do dano e nem a cor.  Usei esse tutorial como base: 
      Pois ele é derivado. E o outro tutorial que usei foi: 
      Porém nesse, não consegui achar a const.h, e quando fui nos arquivos do creaturescript e adicionei uma cor nova a "COLOR_FAIRY", quando abro o jogo, os pokemons que seriam teoricamente "fada", o que eu usei de teste foi a Clefable. A Clefable tomava IK e dava IK no seu atk do tipo fada. 
      Além de que, o meu erro principal é esse: Warning - Monsters::loadMonster] Unknown race type fairy. (data/monster/pokes/geracao 1/Clefable.xml)
       Pois como eu já disse, não consigo achar onde adicionar uma nova race.

    • Por yuriowns
      Salve rapazes, tranquilo? Preciso de ajuda pra colocar para os npc's que vendem pots verificarem quantos itens possuem no tile em que o player está e se tiver com +80 itens no sqm, o npc avisa e não vende nada até o player ir em um sqm com menos de 80 itens no chão.
       
    • Por A.Mokk
      .Qual servidor ou website você utiliza como base? 
      TFS 0.4
      Qual o motivo deste tópico? 
      Bom pessoal, a algumas semanas atras eu joguei um servidor que havia sistema de imbuimento sendo 8.60, no servidor se utilizava a spellwand para encantar as armas, os comandos eram dado no canal Imbuiment... Gostaria de saber se alguém teria como disponibilizar algum sistema de imbuimento, já procurei pra caramba aqui no fórum mas tudo que encontro é pra versões acima da que eu uso.
       
    • Por Mateus Robeerto
      Não sei se aqui é a área ou algum local para solicitar a alteração do email antigo... Não lembro mais a senha dele, nem a resposta secreta para acessar. Peço a algum administrador ou moderador para, por favor, alterar o email para o novo.
×
×
  • Criar Novo...

Informação Importante

Confirmação de Termo