mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-04-30 01:29:21 +02:00
3781 lines
86 KiB
C++
3781 lines
86 KiB
C++
/**
|
|
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
|
|
* Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
|
|
*
|
|
* 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"
|
|
|
|
extern ConfigManager g_config;
|
|
extern Game g_game;
|
|
extern Chat* g_chat;
|
|
extern Vocations g_vocations;
|
|
extern MoveEvents* g_moveEvents;
|
|
extern CreatureEvents* g_creatureEvents;
|
|
extern Events* g_events;
|
|
|
|
MuteCountMap Player::muteCountMap;
|
|
|
|
uint32_t Player::playerAutoID = 0x10000000;
|
|
|
|
Player::Player(ProtocolGame_ptr p) :
|
|
Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p))
|
|
{
|
|
}
|
|
|
|
Player::~Player()
|
|
{
|
|
for (Item* item : inventory) {
|
|
if (item) {
|
|
item->setParent(nullptr);
|
|
item->decrementReferenceCounter();
|
|
}
|
|
}
|
|
|
|
for (const auto& it : depotLockerMap) {
|
|
it.second->decrementReferenceCounter();
|
|
}
|
|
|
|
setWriteItem(nullptr);
|
|
setEditHouse(nullptr);
|
|
}
|
|
|
|
bool Player::setVocation(uint16_t vocId)
|
|
{
|
|
Vocation* voc = g_vocations.getVocation(vocId);
|
|
if (!voc) {
|
|
return false;
|
|
}
|
|
vocation = voc;
|
|
|
|
Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT);
|
|
if (condition) {
|
|
condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->getHealthGainAmount());
|
|
condition->setParam(CONDITION_PARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000);
|
|
condition->setParam(CONDITION_PARAM_MANAGAIN, vocation->getManaGainAmount());
|
|
condition->setParam(CONDITION_PARAM_MANATICKS, vocation->getManaGainTicks() * 1000);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Player::isPushable() const
|
|
{
|
|
if (hasFlag(PlayerFlag_CannotBePushed)) {
|
|
return false;
|
|
}
|
|
return Creature::isPushable();
|
|
}
|
|
|
|
std::string Player::getDescription(int32_t lookDistance) const
|
|
{
|
|
std::ostringstream s;
|
|
|
|
if (lookDistance == -1) {
|
|
s << "yourself.";
|
|
|
|
if (group->access) {
|
|
s << " You are " << group->name << '.';
|
|
} else if (vocation->getId() != VOCATION_NONE) {
|
|
s << " You are " << vocation->getVocDescription() << '.';
|
|
} else {
|
|
s << " You have no vocation.";
|
|
}
|
|
} else {
|
|
s << name;
|
|
if (!group->access) {
|
|
s << " (Level " << level << ')';
|
|
}
|
|
s << '.';
|
|
|
|
if (sex == PLAYERSEX_FEMALE) {
|
|
s << " She";
|
|
} else {
|
|
s << " He";
|
|
}
|
|
|
|
if (group->access) {
|
|
s << " is " << group->name << '.';
|
|
} else if (vocation->getId() != VOCATION_NONE) {
|
|
s << " is " << vocation->getVocDescription() << '.';
|
|
} else {
|
|
s << " has no vocation.";
|
|
}
|
|
}
|
|
|
|
if (guild && guildRank) {
|
|
if (lookDistance == -1) {
|
|
s << " You are ";
|
|
} else if (sex == PLAYERSEX_FEMALE) {
|
|
s << " She is ";
|
|
} else {
|
|
s << " He is ";
|
|
}
|
|
|
|
s << guildRank->name << " of the " << guild->getName();
|
|
if (!guildNick.empty()) {
|
|
s << " (" << guildNick << ')';
|
|
}
|
|
|
|
s << ".";
|
|
}
|
|
|
|
return s.str();
|
|
}
|
|
|
|
Item* Player::getInventoryItem(slots_t slot) const
|
|
{
|
|
if (slot < CONST_SLOT_FIRST || slot > CONST_SLOT_LAST) {
|
|
return nullptr;
|
|
}
|
|
return inventory[slot];
|
|
}
|
|
|
|
void Player::addConditionSuppressions(uint32_t conditions)
|
|
{
|
|
conditionSuppressions |= conditions;
|
|
}
|
|
|
|
void Player::removeConditionSuppressions(uint32_t conditions)
|
|
{
|
|
conditionSuppressions &= ~conditions;
|
|
}
|
|
|
|
Item* Player::getWeapon() const
|
|
{
|
|
Item* item = inventory[CONST_SLOT_LEFT];
|
|
if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) {
|
|
return item;
|
|
}
|
|
|
|
item = inventory[CONST_SLOT_RIGHT];
|
|
if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) {
|
|
return item;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Item* Player::getAmmunition() const
|
|
{
|
|
return inventory[CONST_SLOT_AMMO];
|
|
}
|
|
|
|
int32_t Player::getArmor() const
|
|
{
|
|
int32_t armor = 0; // base armor
|
|
|
|
static const slots_t armorSlots[] = { CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING };
|
|
for (slots_t slot : armorSlots) {
|
|
Item* inventoryItem = inventory[slot];
|
|
if (inventoryItem) {
|
|
armor += inventoryItem->getArmor();
|
|
}
|
|
}
|
|
|
|
if (armor > 1) {
|
|
armor = rand() % (armor >> 1) + (armor >> 1);
|
|
}
|
|
|
|
return armor;
|
|
}
|
|
|
|
void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const
|
|
{
|
|
shield = nullptr;
|
|
weapon = nullptr;
|
|
|
|
for (uint32_t slot = CONST_SLOT_RIGHT; slot <= CONST_SLOT_LEFT; slot++) {
|
|
Item* item = inventory[slot];
|
|
if (!item) {
|
|
continue;
|
|
}
|
|
|
|
switch (item->getWeaponType()) {
|
|
case WEAPON_NONE:
|
|
break;
|
|
|
|
case WEAPON_SHIELD: {
|
|
if (!shield || item->getDefense() > shield->getDefense()) {
|
|
shield = item;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: { // weapons that are not shields
|
|
weapon = item;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t Player::getDefense()
|
|
{
|
|
int32_t totalDefense = 5;
|
|
int32_t defenseSkill = getSkillLevel(SKILL_FIST);
|
|
|
|
const Item* weapon;
|
|
const Item* shield;
|
|
getShieldAndWeapon(shield, weapon);
|
|
|
|
if (weapon) {
|
|
totalDefense = weapon->getDefense() + 1;
|
|
|
|
switch (weapon->getWeaponType()) {
|
|
case WEAPON_AXE:
|
|
defenseSkill = SKILL_AXE;
|
|
break;
|
|
case WEAPON_SWORD:
|
|
defenseSkill = SKILL_SWORD;
|
|
break;
|
|
case WEAPON_CLUB:
|
|
defenseSkill = SKILL_CLUB;
|
|
break;
|
|
case WEAPON_DISTANCE:
|
|
case WEAPON_AMMO:
|
|
defenseSkill = SKILL_DISTANCE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shield) {
|
|
totalDefense = shield->getDefense() + 1;
|
|
defenseSkill = getSkillLevel(SKILL_SHIELD);
|
|
}
|
|
|
|
fightMode_t attackMode = getFightMode();
|
|
|
|
if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) {
|
|
attackMode = FIGHTMODE_DEFENSE;
|
|
}
|
|
|
|
if (attackMode == FIGHTMODE_ATTACK) {
|
|
totalDefense -= 4 * totalDefense / 10;
|
|
} else if (attackMode == FIGHTMODE_DEFENSE) {
|
|
totalDefense += 8 * totalDefense / 10;
|
|
}
|
|
|
|
if (totalDefense) {
|
|
int32_t formula = (5 * (defenseSkill) + 50) * totalDefense;
|
|
int32_t randresult = rand() % 100;
|
|
|
|
totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.;
|
|
}
|
|
|
|
return totalDefense;
|
|
}
|
|
|
|
fightMode_t Player::getFightMode() const
|
|
{
|
|
return fightMode;
|
|
}
|
|
|
|
uint16_t Player::getClientIcons() const
|
|
{
|
|
uint16_t icons = 0;
|
|
for (Condition* condition : conditions) {
|
|
if (!isSuppress(condition->getType())) {
|
|
icons |= condition->getIcons();
|
|
}
|
|
}
|
|
|
|
// Game client debugs with 10 or more icons
|
|
// so let's prevent that from happening.
|
|
std::bitset<20> icon_bitset(static_cast<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)
|
|
{
|
|
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) {
|
|
storageMap[key] = value;
|
|
} else {
|
|
storageMap.erase(key);
|
|
}
|
|
}
|
|
|
|
bool Player::getStorageValue(const uint32_t key, int32_t& value) const
|
|
{
|
|
auto it = storageMap.find(key);
|
|
if (it == storageMap.end()) {
|
|
value = 0;
|
|
return false;
|
|
}
|
|
|
|
value = it->second;
|
|
return true;
|
|
}
|
|
|
|
bool Player::canSee(const Position& pos) const
|
|
{
|
|
if (!client) {
|
|
return false;
|
|
}
|
|
return client->canSee(pos);
|
|
}
|
|
|
|
bool Player::canSeeCreature(const Creature* creature) const
|
|
{
|
|
if (creature == this) {
|
|
return true;
|
|
}
|
|
|
|
if (creature->isInGhostMode() && !group->access) {
|
|
return false;
|
|
}
|
|
|
|
if (!creature->getPlayer() && !canSeeInvisibility() && creature->isInvisible()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Player::onReceiveMail() 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;
|
|
}
|
|
|
|
DepotLocker* Player::getDepotLocker(uint32_t depotId, bool autoCreate)
|
|
{
|
|
auto it = depotLockerMap.find(depotId);
|
|
if (it != depotLockerMap.end()) {
|
|
return it->second;
|
|
}
|
|
|
|
if (autoCreate) {
|
|
DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1);
|
|
depotLocker->setDepotId(depotId);
|
|
Item* depotItem = Item::CreateItem(ITEM_DEPOT);
|
|
if (depotItem) {
|
|
depotLocker->internalAddThing(depotItem);
|
|
}
|
|
depotLockerMap[depotId] = depotLocker;
|
|
return depotLocker;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Player::sendCancelMessage(ReturnValue message) const
|
|
{
|
|
sendCancelMessage(getReturnMessage(message));
|
|
}
|
|
|
|
void Player::sendStats()
|
|
{
|
|
if (client) {
|
|
client->sendStats();
|
|
}
|
|
}
|
|
|
|
void Player::sendPing()
|
|
{
|
|
int64_t timeNow = OTSYS_TIME();
|
|
|
|
bool hasLostConnection = false;
|
|
if ((timeNow - lastPing) >= 5000) {
|
|
lastPing = timeNow;
|
|
if (client) {
|
|
client->sendPing();
|
|
} else {
|
|
hasLostConnection = true;
|
|
}
|
|
}
|
|
|
|
int64_t noPongTime = timeNow - lastPong;
|
|
if ((hasLostConnection || noPongTime >= 7000) && attackedCreature && attackedCreature->getPlayer()) {
|
|
setAttackedCreature(nullptr);
|
|
}
|
|
|
|
if (noPongTime >= 60000 && canLogout()) {
|
|
if (g_creatureEvents->playerLogout(this)) {
|
|
if (client) {
|
|
client->logout(true, true);
|
|
} else {
|
|
g_game.removeCreature(this, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item* Player::getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen)
|
|
{
|
|
windowTextId = this->windowTextId;
|
|
maxWriteLen = this->maxWriteLen;
|
|
return writeItem;
|
|
}
|
|
|
|
void Player::setWriteItem(Item* item, uint16_t maxWriteLen /*= 0*/)
|
|
{
|
|
windowTextId++;
|
|
|
|
if (writeItem) {
|
|
writeItem->decrementReferenceCounter();
|
|
}
|
|
|
|
if (item) {
|
|
writeItem = item;
|
|
this->maxWriteLen = maxWriteLen;
|
|
writeItem->incrementReferenceCounter();
|
|
} else {
|
|
writeItem = nullptr;
|
|
this->maxWriteLen = 0;
|
|
}
|
|
}
|
|
|
|
House* Player::getEditHouse(uint32_t& windowTextId, uint32_t& listId)
|
|
{
|
|
windowTextId = this->windowTextId;
|
|
listId = this->editListId;
|
|
return editHouse;
|
|
}
|
|
|
|
void Player::setEditHouse(House* house, uint32_t listId /*= 0*/)
|
|
{
|
|
windowTextId++;
|
|
editHouse = house;
|
|
editListId = listId;
|
|
}
|
|
|
|
void Player::sendHouseWindow(House* house, uint32_t listId) const
|
|
{
|
|
if (!client) {
|
|
return;
|
|
}
|
|
|
|
std::string text;
|
|
if (house->getAccessList(listId, text)) {
|
|
client->sendHouseWindow(windowTextId, text);
|
|
}
|
|
}
|
|
|
|
//container
|
|
void Player::sendAddContainerItem(const Container* container, const Item* item)
|
|
{
|
|
if (!client) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& it : openContainers) {
|
|
const OpenContainer& openContainer = it.second;
|
|
if (openContainer.container != container) {
|
|
continue;
|
|
}
|
|
|
|
if (openContainer.index >= container->capacity()) {
|
|
item = container->getItemByIndex(openContainer.index - 1);
|
|
}
|
|
client->sendAddContainerItem(it.first, item);
|
|
}
|
|
}
|
|
|
|
void Player::sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* newItem)
|
|
{
|
|
if (!client) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& it : openContainers) {
|
|
const OpenContainer& openContainer = it.second;
|
|
if (openContainer.container != container) {
|
|
continue;
|
|
}
|
|
|
|
if (slot < openContainer.index) {
|
|
continue;
|
|
}
|
|
|
|
uint16_t pageEnd = openContainer.index + container->capacity();
|
|
if (slot >= pageEnd) {
|
|
continue;
|
|
}
|
|
|
|
client->sendUpdateContainerItem(it.first, slot, newItem);
|
|
}
|
|
}
|
|
|
|
void Player::sendRemoveContainerItem(const Container* container, uint16_t slot)
|
|
{
|
|
if (!client) {
|
|
return;
|
|
}
|
|
|
|
for (auto& it : openContainers) {
|
|
OpenContainer& openContainer = it.second;
|
|
if (openContainer.container != container) {
|
|
continue;
|
|
}
|
|
|
|
uint16_t& firstIndex = openContainer.index;
|
|
if (firstIndex > 0 && firstIndex >= container->size() - 1) {
|
|
firstIndex -= container->capacity();
|
|
sendContainer(it.first, container, false, firstIndex);
|
|
}
|
|
|
|
client->sendRemoveContainerItem(it.first, std::max<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);
|
|
}
|
|
|
|
std::cout << name << " has logged in." << std::endl;
|
|
|
|
if (guild) {
|
|
guild->addMember(this);
|
|
}
|
|
|
|
int32_t offlineTime;
|
|
if (getLastLogout() != 0) {
|
|
// Not counting more than 21 days to prevent overflow when multiplying with 1000 (for milliseconds).
|
|
offlineTime = std::min<int32_t>(time(nullptr) - getLastLogout(), 86400 * 21);
|
|
} else {
|
|
offlineTime = 0;
|
|
}
|
|
|
|
for (Condition* condition : getMuteConditions()) {
|
|
condition->setTicks(condition->getTicks() - (offlineTime * 1000));
|
|
if (condition->getTicks() <= 0) {
|
|
removeCondition(condition);
|
|
}
|
|
}
|
|
|
|
g_game.checkPlayersRecord();
|
|
IOLoginData::updateOnlineStatus(guid, true);
|
|
}
|
|
}
|
|
|
|
void Player::onAttackedCreatureDisappear(bool isLogout)
|
|
{
|
|
sendCancelTarget();
|
|
|
|
if (!isLogout) {
|
|
sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost.");
|
|
}
|
|
}
|
|
|
|
void Player::onFollowCreatureDisappear(bool isLogout)
|
|
{
|
|
sendCancelTarget();
|
|
|
|
if (!isLogout) {
|
|
sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost.");
|
|
}
|
|
}
|
|
|
|
void Player::onChangeZone(ZoneType_t zone)
|
|
{
|
|
if (zone == ZONE_PROTECTION) {
|
|
if (attackedCreature && !hasFlag(PlayerFlag_IgnoreProtectionZone)) {
|
|
setAttackedCreature(nullptr);
|
|
onAttackedCreatureDisappear(false);
|
|
}
|
|
}
|
|
|
|
sendIcons();
|
|
}
|
|
|
|
void Player::onAttackedCreatureChangeZone(ZoneType_t zone)
|
|
{
|
|
if (zone == ZONE_PROTECTION) {
|
|
if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) {
|
|
setAttackedCreature(nullptr);
|
|
onAttackedCreatureDisappear(false);
|
|
}
|
|
} else if (zone == ZONE_NOPVP) {
|
|
if (attackedCreature->getPlayer()) {
|
|
if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) {
|
|
setAttackedCreature(nullptr);
|
|
onAttackedCreatureDisappear(false);
|
|
}
|
|
}
|
|
} else if (zone == ZONE_NORMAL) {
|
|
//attackedCreature can leave a pvp zone if not pzlocked
|
|
if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) {
|
|
if (attackedCreature->getPlayer()) {
|
|
setAttackedCreature(nullptr);
|
|
onAttackedCreatureDisappear(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::onRemoveCreature(Creature* creature, bool isLogout)
|
|
{
|
|
Creature::onRemoveCreature(creature, isLogout);
|
|
|
|
if (creature == this) {
|
|
if (isLogout) {
|
|
loginPosition = getPosition();
|
|
}
|
|
|
|
lastLogout = time(nullptr);
|
|
|
|
if (eventWalk != 0) {
|
|
setFollowCreature(nullptr);
|
|
}
|
|
|
|
if (tradePartner) {
|
|
g_game.internalCloseTrade(this);
|
|
}
|
|
|
|
clearPartyInvitations();
|
|
|
|
if (party) {
|
|
party->leaveParty(this);
|
|
}
|
|
|
|
g_chat->removeUserFromAllChannels(*this);
|
|
|
|
std::cout << getName() << " has logged out." << std::endl;
|
|
|
|
if (guild) {
|
|
guild->removeMember(this);
|
|
}
|
|
|
|
IOLoginData::updateOnlineStatus(guid, false);
|
|
|
|
bool saved = false;
|
|
for (uint32_t tries = 0; tries < 3; ++tries) {
|
|
if (IOLoginData::savePlayer(this)) {
|
|
saved = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!saved) {
|
|
std::cout << "Error while saving player: " << getName() << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::onWalk(Direction& dir)
|
|
{
|
|
Creature::onWalk(dir);
|
|
setNextActionTask(nullptr);
|
|
setNextAction(OTSYS_TIME() + getStepDuration(dir));
|
|
}
|
|
|
|
void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos,
|
|
const Tile* oldTile, const Position& oldPos, bool teleport)
|
|
{
|
|
Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport);
|
|
|
|
if (hasFollowPath && (creature == followCreature || (creature == this && followCreature))) {
|
|
isUpdatingPath = false;
|
|
g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID())));
|
|
}
|
|
|
|
if (creature != this) {
|
|
return;
|
|
}
|
|
|
|
if (tradeState != TRADE_TRANSFER) {
|
|
//check if we should close trade
|
|
if (tradeItem && !Position::areInRange<1, 1, 0>(tradeItem->getPosition(), getPosition())) {
|
|
g_game.internalCloseTrade(this);
|
|
}
|
|
|
|
if (tradePartner && !Position::areInRange<2, 2, 0>(tradePartner->getPosition(), getPosition())) {
|
|
g_game.internalCloseTrade(this);
|
|
}
|
|
}
|
|
|
|
if (party) {
|
|
party->updateSharedExperience();
|
|
}
|
|
|
|
if (teleport || oldPos.z != newPos.z) {
|
|
int32_t ticks = g_config.getNumber(ConfigManager::STAIRHOP_DELAY);
|
|
if (ticks > 0) {
|
|
if (Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_PACIFIED, ticks, 0)) {
|
|
addCondition(condition);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//container
|
|
void Player::onAddContainerItem(const Item* item)
|
|
{
|
|
checkTradeState(item);
|
|
}
|
|
|
|
void Player::onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem)
|
|
{
|
|
if (oldItem != newItem) {
|
|
onRemoveContainerItem(container, oldItem);
|
|
}
|
|
|
|
if (tradeState != TRADE_TRANSFER) {
|
|
checkTradeState(oldItem);
|
|
}
|
|
}
|
|
|
|
void Player::onRemoveContainerItem(const Container* container, const Item* item)
|
|
{
|
|
if (tradeState != TRADE_TRANSFER) {
|
|
checkTradeState(item);
|
|
|
|
if (tradeItem) {
|
|
if (tradeItem->getParent() != container && container->isHoldingItem(tradeItem)) {
|
|
g_game.internalCloseTrade(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::onCloseContainer(const Container* container)
|
|
{
|
|
if (!client) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& it : openContainers) {
|
|
if (it.second.container == container) {
|
|
client->sendCloseContainer(it.first);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::onSendContainer(const Container* container)
|
|
{
|
|
if (!client) {
|
|
return;
|
|
}
|
|
|
|
bool hasParent = container->hasParent();
|
|
for (const auto& it : openContainers) {
|
|
const OpenContainer& openContainer = it.second;
|
|
if (openContainer.container == container) {
|
|
client->sendContainer(it.first, container, hasParent, openContainer.index);
|
|
}
|
|
}
|
|
}
|
|
|
|
//inventory
|
|
void Player::onUpdateInventoryItem(Item* oldItem, Item* newItem)
|
|
{
|
|
if (oldItem != newItem) {
|
|
onRemoveInventoryItem(oldItem);
|
|
}
|
|
|
|
if (tradeState != TRADE_TRANSFER) {
|
|
checkTradeState(oldItem);
|
|
}
|
|
}
|
|
|
|
void Player::onRemoveInventoryItem(Item* item)
|
|
{
|
|
if (tradeState != TRADE_TRANSFER) {
|
|
checkTradeState(item);
|
|
|
|
if (tradeItem) {
|
|
const Container* container = item->getContainer();
|
|
if (container && container->isHoldingItem(tradeItem)) {
|
|
g_game.internalCloseTrade(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::checkTradeState(const Item* item)
|
|
{
|
|
if (!tradeItem || tradeState == TRADE_TRANSFER) {
|
|
return;
|
|
}
|
|
|
|
if (tradeItem == item) {
|
|
g_game.internalCloseTrade(this);
|
|
} else {
|
|
const Container* container = dynamic_cast<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 ((!pzLocked && OTSYS_TIME() - lastPong >= 60000) || idleTime > (kickAfterMinutes * 60000) + 60000) {
|
|
kickPlayer(true);
|
|
} else if (client && idleTime == 60000 * kickAfterMinutes) {
|
|
std::ostringstream ss;
|
|
ss << "You have been idle for " << kickAfterMinutes << " minutes. You will be disconnected in one minute if you are still idle then.";
|
|
client->sendTextMessage(TextMessage(MESSAGE_STATUS_WARNING, ss.str()));
|
|
}
|
|
}
|
|
|
|
if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) {
|
|
checkSkullTicks();
|
|
}
|
|
}
|
|
|
|
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) {
|
|
g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(exp));
|
|
}
|
|
|
|
uint32_t prevLevel = level;
|
|
while (experience >= nextLevelExp) {
|
|
++level;
|
|
healthMax += vocation->getHPGain();
|
|
health += vocation->getHPGain();
|
|
manaMax += vocation->getManaGain();
|
|
mana += vocation->getManaGain();
|
|
capacity += vocation->getCapGain();
|
|
|
|
currLevelExp = nextLevelExp;
|
|
nextLevelExp = Player::getExpForLevel(level + 1);
|
|
if (currLevelExp >= nextLevelExp) {
|
|
//player has reached max level
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (prevLevel != level) {
|
|
updateBaseSpeed();
|
|
setBaseSpeed(getBaseSpeed());
|
|
|
|
g_game.changeSpeed(this, 0);
|
|
g_game.addCreatureHealth(this);
|
|
|
|
if (party) {
|
|
party->updateSharedExperience();
|
|
}
|
|
|
|
g_creatureEvents->playerAdvance(this, SKILL_LEVEL, prevLevel, level);
|
|
|
|
std::ostringstream ss;
|
|
ss << "You advanced from Level " << prevLevel << " to Level " << level << '.';
|
|
sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str());
|
|
}
|
|
|
|
if (nextLevelExp > currLevelExp) {
|
|
levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp);
|
|
} else {
|
|
levelPercent = 0;
|
|
}
|
|
sendStats();
|
|
}
|
|
|
|
void Player::removeExperience(uint64_t exp)
|
|
{
|
|
if (experience == 0 || exp == 0) {
|
|
return;
|
|
}
|
|
|
|
experience = std::max<int64_t>(0, experience - exp);
|
|
|
|
uint32_t oldLevel = level;
|
|
uint64_t currLevelExp = Player::getExpForLevel(level);
|
|
|
|
while (level > 1 && experience < currLevelExp) {
|
|
--level;
|
|
healthMax = std::max<int32_t>(150, std::max<int32_t>(0, healthMax - vocation->getHPGain()));
|
|
manaMax = std::max<int32_t>(0, manaMax - vocation->getManaGain());
|
|
capacity = std::max<int32_t>(400, std::max<int32_t>(0, capacity - vocation->getCapGain()));
|
|
currLevelExp = Player::getExpForLevel(level);
|
|
}
|
|
|
|
if (oldLevel != level) {
|
|
health = healthMax;
|
|
mana = manaMax;
|
|
|
|
updateBaseSpeed();
|
|
setBaseSpeed(getBaseSpeed());
|
|
|
|
g_game.changeSpeed(this, 0);
|
|
g_game.addCreatureHealth(this);
|
|
|
|
if (party) {
|
|
party->updateSharedExperience();
|
|
}
|
|
|
|
std::ostringstream ss;
|
|
ss << "You were downgraded from Level " << oldLevel << " to Level " << level << '.';
|
|
sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str());
|
|
}
|
|
|
|
uint64_t nextLevelExp = Player::getExpForLevel(level + 1);
|
|
if (nextLevelExp > currLevelExp) {
|
|
levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp);
|
|
} else {
|
|
levelPercent = 0;
|
|
}
|
|
sendStats();
|
|
}
|
|
|
|
uint8_t Player::getPercentLevel(uint64_t count, uint64_t nextLevelCount)
|
|
{
|
|
if (nextLevelCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
uint8_t result = (count * 100) / nextLevelCount;
|
|
if (result > 100) {
|
|
return 0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
uint16_t Player::getDropLootPercent()
|
|
{
|
|
return 10;
|
|
}
|
|
|
|
void Player::onBlockHit()
|
|
{
|
|
if (shieldBlockCount > 0) {
|
|
--shieldBlockCount;
|
|
|
|
if (hasShield()) {
|
|
addSkillAdvance(SKILL_SHIELD, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::onAttackedCreatureBlockHit(BlockType_t blockType)
|
|
{
|
|
lastAttackBlockType = blockType;
|
|
|
|
switch (blockType) {
|
|
case BLOCK_NONE: {
|
|
addAttackSkillPoint = true;
|
|
bloodHitCount = 30;
|
|
shieldBlockCount = 30;
|
|
break;
|
|
}
|
|
|
|
case BLOCK_DEFENSE:
|
|
case BLOCK_ARMOR: {
|
|
//need to draw blood every 30 hits
|
|
if (bloodHitCount > 0) {
|
|
addAttackSkillPoint = true;
|
|
--bloodHitCount;
|
|
} else {
|
|
addAttackSkillPoint = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
addAttackSkillPoint = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Player::hasShield() const
|
|
{
|
|
Item* item = inventory[CONST_SLOT_LEFT];
|
|
if (item && item->getWeaponType() == WEAPON_SHIELD) {
|
|
return true;
|
|
}
|
|
|
|
item = inventory[CONST_SLOT_RIGHT];
|
|
if (item && item->getWeaponType() == WEAPON_SHIELD) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage,
|
|
bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/)
|
|
{
|
|
BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field);
|
|
|
|
if (attacker) {
|
|
sendCreatureSquare(attacker, SQ_COLOR_BLACK);
|
|
}
|
|
|
|
if (blockType != BLOCK_NONE) {
|
|
return blockType;
|
|
}
|
|
|
|
if (damage > 0) {
|
|
for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) {
|
|
if (!isItemAbilityEnabled(static_cast<slots_t>(slot))) {
|
|
continue;
|
|
}
|
|
|
|
Item* item = inventory[slot];
|
|
if (!item) {
|
|
continue;
|
|
}
|
|
|
|
const ItemType& it = Item::items[item->getID()];
|
|
if (it.abilities) {
|
|
const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)];
|
|
if (absorbPercent != 0) {
|
|
damage -= std::round(damage * (absorbPercent / 100.));
|
|
|
|
uint16_t charges = item->getCharges() - 1;
|
|
if (charges != 0) {
|
|
g_game.transformItem(item, item->getID(), charges);
|
|
} else {
|
|
g_game.internalRemoveItem(item);
|
|
}
|
|
}
|
|
|
|
if (field) {
|
|
const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)];
|
|
if (fieldAbsorbPercent != 0) {
|
|
damage -= std::round(damage * (fieldAbsorbPercent / 100.));
|
|
|
|
uint16_t charges = item->getCharges();
|
|
if (charges != 0) {
|
|
if (charges - 1 == 0) {
|
|
g_game.internalRemoveItem(item);
|
|
} else {
|
|
g_game.transformItem(item, item->getID(), charges - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (damage <= 0) {
|
|
damage = 0;
|
|
blockType = BLOCK_ARMOR;
|
|
}
|
|
}
|
|
|
|
return blockType;
|
|
}
|
|
|
|
uint32_t Player::getIP() const
|
|
{
|
|
if (client) {
|
|
return client->getIP();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Player::dropLoot(Container* corpse, Creature*)
|
|
{
|
|
if (corpse && lootDrop) {
|
|
Skulls_t playerSkull = getSkull();
|
|
if (inventory[CONST_SLOT_NECKLACE] && inventory[CONST_SLOT_NECKLACE]->getID() == ITEM_AMULETOFLOSS && playerSkull != SKULL_RED) {
|
|
g_game.internalRemoveItem(inventory[CONST_SLOT_NECKLACE], 1);
|
|
} else {
|
|
for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
|
|
Item* item = inventory[i];
|
|
if (item) {
|
|
if (playerSkull == SKULL_RED || item->getContainer() || uniform_random(1, 100) <= getDropLootPercent()) {
|
|
g_game.internalMoveItem(this, corpse, INDEX_WHEREEVER, item, item->getItemCount(), 0);
|
|
sendInventoryItem(static_cast<slots_t>(i), nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::death(Creature* lastHitCreature)
|
|
{
|
|
loginPosition = town->getTemplePosition();
|
|
|
|
if (skillLoss) {
|
|
//Magic level loss
|
|
uint64_t sumMana = 0;
|
|
uint64_t lostMana = 0;
|
|
|
|
//sum up all the mana
|
|
for (uint32_t i = 1; i <= magLevel; ++i) {
|
|
sumMana += vocation->getReqMana(i);
|
|
}
|
|
|
|
sumMana += manaSpent;
|
|
|
|
double deathLossPercent = getLostPercent();
|
|
|
|
lostMana = static_cast<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;
|
|
|
|
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 (Player::lastHitIsPlayer(lastHitCreature)) {
|
|
bitset.reset(5);
|
|
blessings = bitset.to_ulong();
|
|
} else {
|
|
blessings = 32;
|
|
}
|
|
} else {
|
|
blessings = 0;
|
|
}
|
|
|
|
sendStats();
|
|
sendSkills();
|
|
|
|
health = healthMax;
|
|
mana = manaMax;
|
|
|
|
auto it = conditions.begin(), end = conditions.end();
|
|
while (it != end) {
|
|
Condition* condition = *it;
|
|
if (condition->isPersistent()) {
|
|
it = conditions.erase(it);
|
|
|
|
condition->endCondition(this);
|
|
onEndCondition(condition->getType());
|
|
delete condition;
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
// Teleport newbies to newbie island
|
|
if (g_config.getBoolean(ConfigManager::TELEPORT_NEWBIES)) {
|
|
if (getVocationId() != VOCATION_NONE && level <= static_cast<uint32_t>(g_config.getNumber(ConfigManager::NEWBIE_LEVEL_THRESHOLD))) {
|
|
Town* newbieTown = g_game.map.towns.getTown(g_config.getNumber(ConfigManager::NEWBIE_TOWN));
|
|
if (newbieTown) {
|
|
// Restart stats
|
|
level = 1;
|
|
experience = 0;
|
|
levelPercent = 0;
|
|
capacity = 400;
|
|
health = 150;
|
|
healthMax = 150;
|
|
mana = 0;
|
|
manaMax = 0;
|
|
magLevel = 0;
|
|
magLevelPercent = 0;
|
|
manaSpent = 0;
|
|
setVocation(0);
|
|
|
|
// Restart skills
|
|
for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill
|
|
skills[i].level = 10;
|
|
skills[i].tries = 0;
|
|
skills[i].percent = 0;
|
|
}
|
|
|
|
// Restart town
|
|
setTown(newbieTown);
|
|
loginPosition = getTemplePosition();
|
|
|
|
// Restart first items
|
|
lastLoginSaved = 0;
|
|
lastLogout = 0;
|
|
|
|
|
|
// Restart items
|
|
for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; slot++)
|
|
{
|
|
Item* item = inventory[slot];
|
|
if (item) {
|
|
g_game.internalRemoveItem(item, item->getItemCount());
|
|
}
|
|
}
|
|
} else {
|
|
std::cout << "[Warning - Player:death] Newbie teletransportation is enabled, newbie town does not exist." << std::endl;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
setLossSkill(true);
|
|
|
|
auto it = conditions.begin(), end = conditions.end();
|
|
while (it != end) {
|
|
Condition* condition = *it;
|
|
if (condition->isPersistent()) {
|
|
it = conditions.erase(it);
|
|
|
|
condition->endCondition(this);
|
|
onEndCondition(condition->getType());
|
|
delete condition;
|
|
}
|
|
else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
health = healthMax;
|
|
g_game.internalTeleport(this, getTemplePosition(), true);
|
|
g_game.addCreatureHealth(this);
|
|
onThink(EVENT_CREATURE_THINK_INTERVAL);
|
|
onIdleStatus();
|
|
sendStats();
|
|
}
|
|
}
|
|
|
|
bool Player::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified)
|
|
{
|
|
if (getZone() != ZONE_PVP || !Player::lastHitIsPlayer(lastHitCreature)) {
|
|
return Creature::dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified);
|
|
}
|
|
|
|
setDropLoot(true);
|
|
return false;
|
|
}
|
|
|
|
Item* Player::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature)
|
|
{
|
|
Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature);
|
|
if (corpse && corpse->getContainer()) {
|
|
std::ostringstream ss;
|
|
if (lastHitCreature) {
|
|
ss << "You recognize " << getNameDescription() << ". " << (getSex() == PLAYERSEX_FEMALE ? "She" : "He") << " was killed by " << lastHitCreature->getNameDescription() << '.';
|
|
} else {
|
|
ss << "You recognize " << getNameDescription() << '.';
|
|
}
|
|
|
|
corpse->setSpecialDescription(ss.str());
|
|
}
|
|
return corpse;
|
|
}
|
|
|
|
void Player::addInFightTicks(bool pzlock /*= false*/)
|
|
{
|
|
if (hasFlag(PlayerFlag_NotGainInFight)) {
|
|
return;
|
|
}
|
|
|
|
if (pzlock) {
|
|
pzLocked = true;
|
|
}
|
|
|
|
Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::PZ_LOCKED), 0);
|
|
addCondition(condition);
|
|
}
|
|
|
|
void Player::removeList()
|
|
{
|
|
g_game.removePlayer(this);
|
|
|
|
for (const auto& it : g_game.getPlayers()) {
|
|
it.second->notifyStatusChange(this, VIPSTATUS_OFFLINE);
|
|
}
|
|
}
|
|
|
|
void Player::addList()
|
|
{
|
|
for (const auto& it : g_game.getPlayers()) {
|
|
it.second->notifyStatusChange(this, VIPSTATUS_ONLINE);
|
|
}
|
|
|
|
g_game.addPlayer(this);
|
|
}
|
|
|
|
void Player::kickPlayer(bool displayEffect)
|
|
{
|
|
g_creatureEvents->playerLogout(this);
|
|
if (client) {
|
|
client->logout(displayEffect, true);
|
|
} else {
|
|
g_game.removeCreature(this);
|
|
}
|
|
}
|
|
|
|
void Player::notifyStatusChange(Player* loginPlayer, VipStatus_t status)
|
|
{
|
|
if (!client) {
|
|
return;
|
|
}
|
|
|
|
auto it = VIPList.find(loginPlayer->guid);
|
|
if (it == VIPList.end()) {
|
|
return;
|
|
}
|
|
|
|
client->sendUpdatedVIPStatus(loginPlayer->guid, status);
|
|
|
|
if (status == VIPSTATUS_ONLINE) {
|
|
client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged in."));
|
|
} else if (status == VIPSTATUS_OFFLINE) {
|
|
client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged out."));
|
|
}
|
|
}
|
|
|
|
bool Player::removeVIP(uint32_t vipGuid)
|
|
{
|
|
if (VIPList.erase(vipGuid) == 0) {
|
|
return false;
|
|
}
|
|
|
|
IOLoginData::removeVIPEntry(accountNumber, vipGuid);
|
|
return true;
|
|
}
|
|
|
|
bool Player::addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status)
|
|
{
|
|
if (guid == vipGuid) {
|
|
sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add yourself.");
|
|
return false;
|
|
}
|
|
|
|
if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) {
|
|
sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add more buddies.");
|
|
return false;
|
|
}
|
|
|
|
auto result = VIPList.insert(vipGuid);
|
|
if (!result.second) {
|
|
sendTextMessage(MESSAGE_STATUS_SMALL, "This player is already in your list.");
|
|
return false;
|
|
}
|
|
|
|
IOLoginData::addVIPEntry(accountNumber, vipGuid);
|
|
if (client) {
|
|
client->sendVIP(vipGuid, vipName, status);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Player::addVIPInternal(uint32_t vipGuid)
|
|
{
|
|
if (guid == vipGuid) {
|
|
return false;
|
|
}
|
|
|
|
if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) {
|
|
return false;
|
|
}
|
|
|
|
return VIPList.insert(vipGuid).second;
|
|
}
|
|
|
|
//close container and its child containers
|
|
void Player::autoCloseContainers(const Container* container)
|
|
{
|
|
std::vector<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)) {
|
|
ret = RETURNVALUE_PUTTHISOBJECTINYOURHAND;
|
|
}
|
|
|
|
switch (index) {
|
|
case CONST_SLOT_HEAD: {
|
|
if (slotPosition & SLOTP_HEAD) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_NECKLACE: {
|
|
if (slotPosition & SLOTP_NECKLACE) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_BACKPACK: {
|
|
if (slotPosition & SLOTP_BACKPACK) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_ARMOR: {
|
|
if (slotPosition & SLOTP_ARMOR) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_RIGHT: {
|
|
if (slotPosition & SLOTP_RIGHT) {
|
|
if (slotPosition & SLOTP_TWO_HAND) {
|
|
if (inventory[CONST_SLOT_LEFT] && inventory[CONST_SLOT_LEFT] != item) {
|
|
ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE;
|
|
} else {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
} else if (inventory[CONST_SLOT_LEFT]) {
|
|
const Item* leftItem = inventory[CONST_SLOT_LEFT];
|
|
WeaponType_t type = item->getWeaponType(), leftType = leftItem->getWeaponType();
|
|
|
|
if (leftItem->getSlotPosition() & SLOTP_TWO_HAND) {
|
|
ret = RETURNVALUE_DROPTWOHANDEDITEM;
|
|
} else if (item == leftItem && count == item->getItemCount()) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
} else if (leftType == WEAPON_SHIELD && type == WEAPON_SHIELD) {
|
|
ret = RETURNVALUE_CANONLYUSEONESHIELD;
|
|
} else if (leftType == WEAPON_NONE || type == WEAPON_NONE ||
|
|
leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO
|
|
|| type == WEAPON_SHIELD || type == WEAPON_AMMO) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
} else {
|
|
ret = RETURNVALUE_CANONLYUSEONEWEAPON;
|
|
}
|
|
} else {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_LEFT: {
|
|
if (slotPosition & SLOTP_LEFT) {
|
|
if (slotPosition & SLOTP_TWO_HAND) {
|
|
if (inventory[CONST_SLOT_RIGHT] && inventory[CONST_SLOT_RIGHT] != item) {
|
|
ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE;
|
|
} else {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
} else if (inventory[CONST_SLOT_RIGHT]) {
|
|
const Item* rightItem = inventory[CONST_SLOT_RIGHT];
|
|
WeaponType_t type = item->getWeaponType(), rightType = rightItem->getWeaponType();
|
|
|
|
if (rightItem->getSlotPosition() & SLOTP_TWO_HAND) {
|
|
ret = RETURNVALUE_DROPTWOHANDEDITEM;
|
|
} else if (item == rightItem && count == item->getItemCount()) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
} else if (rightType == WEAPON_SHIELD && type == WEAPON_SHIELD) {
|
|
ret = RETURNVALUE_CANONLYUSEONESHIELD;
|
|
} else if (rightType == WEAPON_NONE || type == WEAPON_NONE ||
|
|
rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO
|
|
|| type == WEAPON_SHIELD || type == WEAPON_AMMO) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
} else {
|
|
ret = RETURNVALUE_CANONLYUSEONEWEAPON;
|
|
}
|
|
} else {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_LEGS: {
|
|
if (slotPosition & SLOTP_LEGS) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_FEET: {
|
|
if (slotPosition & SLOTP_FEET) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_RING: {
|
|
if (slotPosition & SLOTP_RING) {
|
|
ret = RETURNVALUE_NOERROR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_AMMO: {
|
|
ret = RETURNVALUE_NOERROR;
|
|
break;
|
|
}
|
|
|
|
case CONST_SLOT_WHEREEVER:
|
|
case -1:
|
|
ret = RETURNVALUE_NOTENOUGHROOM;
|
|
break;
|
|
|
|
default:
|
|
ret = RETURNVALUE_NOTPOSSIBLE;
|
|
break;
|
|
}
|
|
|
|
if (ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_NOTENOUGHROOM) {
|
|
//need an exchange with source?
|
|
const Item* inventoryItem = getInventoryItem(static_cast<slots_t>(index));
|
|
if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->isRune() || inventoryItem->getID() != item->getID())) {
|
|
return RETURNVALUE_NEEDEXCHANGE;
|
|
}
|
|
|
|
//check if enough capacity
|
|
if (!hasCapacity(item, count)) {
|
|
return RETURNVALUE_NOTENOUGHCAPACITY;
|
|
}
|
|
|
|
if (!g_moveEvents->onPlayerEquip(const_cast<Player*>(this), const_cast<Item*>(item), static_cast<slots_t>(index), true)) {
|
|
return RETURNVALUE_CANNOTBEDRESSED;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount,
|
|
uint32_t flags) const
|
|
{
|
|
const Item* item = thing.getItem();
|
|
if (item == nullptr) {
|
|
maxQueryCount = 0;
|
|
return RETURNVALUE_NOTPOSSIBLE;
|
|
}
|
|
|
|
if (index == INDEX_WHEREEVER) {
|
|
uint32_t n = 0;
|
|
for (int32_t slotIndex = CONST_SLOT_FIRST; slotIndex <= CONST_SLOT_LAST; ++slotIndex) {
|
|
Item* inventoryItem = inventory[slotIndex];
|
|
if (inventoryItem) {
|
|
if (Container* subContainer = inventoryItem->getContainer()) {
|
|
uint32_t queryCount = 0;
|
|
subContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags);
|
|
n += queryCount;
|
|
|
|
//iterate through all items, including sub-containers (deep search)
|
|
for (ContainerIterator it = subContainer->iterator(); it.hasNext(); it.advance()) {
|
|
if (Container* tmpContainer = (*it)->getContainer()) {
|
|
queryCount = 0;
|
|
tmpContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags);
|
|
n += queryCount;
|
|
}
|
|
}
|
|
} else if (inventoryItem->isStackable() && item->equals(inventoryItem) && inventoryItem->getItemCount() < 100) {
|
|
uint32_t remainder = (100 - inventoryItem->getItemCount());
|
|
|
|
if (queryAdd(slotIndex, *item, remainder, flags) == RETURNVALUE_NOERROR) {
|
|
n += remainder;
|
|
}
|
|
}
|
|
} else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot
|
|
if (item->isStackable()) {
|
|
n += 100;
|
|
} else {
|
|
++n;
|
|
}
|
|
}
|
|
}
|
|
|
|
maxQueryCount = n;
|
|
} else {
|
|
const Item* destItem = nullptr;
|
|
|
|
const Thing* destThing = getThing(index);
|
|
if (destThing) {
|
|
destItem = destThing->getItem();
|
|
}
|
|
|
|
if (destItem) {
|
|
if (!destItem->isRune() && 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--;
|
|
}
|
|
|
|
if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) {
|
|
for (Item* tmpContainerItem : tmpContainer->getItemList()) {
|
|
if (Container* subContainer = tmpContainerItem->getContainer()) {
|
|
containers.push_back(subContainer);
|
|
}
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
uint32_t n = 0;
|
|
|
|
for (Item* tmpItem : tmpContainer->getItemList()) {
|
|
if (tmpItem == tradeItem) {
|
|
continue;
|
|
}
|
|
|
|
if (tmpItem == item) {
|
|
continue;
|
|
}
|
|
|
|
//try find an already existing item to stack with
|
|
if (tmpItem->equals(item) && tmpItem->getItemCount() < 100) {
|
|
index = n;
|
|
*destItem = tmpItem;
|
|
return tmpContainer;
|
|
}
|
|
|
|
if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) {
|
|
if (Container* subContainer = tmpItem->getContainer()) {
|
|
containers.push_back(subContainer);
|
|
}
|
|
}
|
|
|
|
n++;
|
|
}
|
|
|
|
if (n < tmpContainer->capacity() && tmpContainer->queryAdd(n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) {
|
|
index = n;
|
|
*destItem = nullptr;
|
|
return tmpContainer;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
Thing* destThing = getThing(index);
|
|
if (destThing) {
|
|
*destItem = destThing->getItem();
|
|
}
|
|
|
|
Cylinder* subCylinder = dynamic_cast<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()) {
|
|
if (container->getID() == itemId) {
|
|
uint32_t itemCount = Item::countByType(item, subType);
|
|
if (itemCount == 0) {
|
|
continue;
|
|
}
|
|
|
|
itemList.push_back(item);
|
|
|
|
count += itemCount;
|
|
if (count >= amount) {
|
|
g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) {
|
|
Item* containerItem = *it;
|
|
if (containerItem->getID() == itemId) {
|
|
uint32_t itemCount = Item::countByType(containerItem, subType);
|
|
if (itemCount == 0) {
|
|
continue;
|
|
}
|
|
|
|
itemList.push_back(containerItem);
|
|
|
|
count += itemCount;
|
|
if (count >= amount) {
|
|
g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::map<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*, 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);
|
|
}
|
|
|
|
if (link == LINK_OWNER || link == LINK_TOPPARENT) {
|
|
updateInventoryWeight();
|
|
updateItemsLight();
|
|
sendStats();
|
|
}
|
|
|
|
if (const Item* item = thing->getItem()) {
|
|
if (const Container* container = item->getContainer()) {
|
|
onSendContainer(container);
|
|
}
|
|
} else if (const Creature* creature = thing->getCreature()) {
|
|
if (creature == this) {
|
|
//check containers
|
|
std::vector<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*, 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));
|
|
}
|
|
|
|
if (link == LINK_OWNER || link == LINK_TOPPARENT) {
|
|
updateInventoryWeight();
|
|
updateItemsLight();
|
|
sendStats();
|
|
}
|
|
|
|
if (const Item* item = thing->getItem()) {
|
|
if (const Container* container = item->getContainer()) {
|
|
if (container->isRemoved() || !Position::areInRange<1, 1, 0>(getPosition(), container->getPosition())) {
|
|
autoCloseContainers(container);
|
|
} else if (container->getTopParent() == this) {
|
|
onSendContainer(container);
|
|
} else if (const Container* topContainer = dynamic_cast<const Container*>(container->getTopParent())) {
|
|
if (const DepotLocker* depotLocker = dynamic_cast<const DepotLocker*>(topContainer)) {
|
|
bool isOwner = false;
|
|
|
|
for (const auto& it : depotLockerMap) {
|
|
if (it.second == depotLocker) {
|
|
isOwner = true;
|
|
onSendContainer(container);
|
|
}
|
|
}
|
|
|
|
if (!isOwner) {
|
|
autoCloseContainers(container);
|
|
}
|
|
} else {
|
|
onSendContainer(container);
|
|
}
|
|
} else {
|
|
autoCloseContainers(container);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::internalAddThing(Thing* thing)
|
|
{
|
|
internalAddThing(0, thing);
|
|
}
|
|
|
|
void Player::internalAddThing(uint32_t index, Thing* thing)
|
|
{
|
|
Item* item = thing->getItem();
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
//index == 0 means we should equip this item at the most appropiate slot (no action required here)
|
|
if (index > 0 && index < 11) {
|
|
if (inventory[index]) {
|
|
return;
|
|
}
|
|
|
|
inventory[index] = item;
|
|
item->setParent(this);
|
|
}
|
|
}
|
|
|
|
uint32_t Player::checkPlayerKilling()
|
|
{
|
|
time_t today = std::time(nullptr);
|
|
int32_t lastDay = 0;
|
|
int32_t lastWeek = 0;
|
|
int32_t lastMonth = 0;
|
|
int64_t egibleMurders = 0;
|
|
|
|
time_t dayTimestamp = today - (24 * 60 * 60);
|
|
time_t weekTimestamp = today - (7 * 24 * 60 * 60);
|
|
time_t monthTimestamp = today - (30 * 24 * 60 * 60);
|
|
|
|
for (time_t currentMurderTimestamp : murderTimeStamps) {
|
|
if (currentMurderTimestamp > dayTimestamp) {
|
|
lastDay++;
|
|
}
|
|
|
|
if (currentMurderTimestamp > weekTimestamp) {
|
|
lastWeek++;
|
|
}
|
|
|
|
egibleMurders = lastMonth + 1;
|
|
|
|
if (currentMurderTimestamp <= monthTimestamp) {
|
|
egibleMurders = lastMonth;
|
|
}
|
|
|
|
lastMonth = egibleMurders;
|
|
}
|
|
|
|
if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_BANISHMENT) ||
|
|
lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_BANISHMENT) ||
|
|
lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_BANISHMENT)) {
|
|
return 2; // banishment!
|
|
}
|
|
|
|
if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_RED_SKULL) ||
|
|
lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_RED_SKULL) ||
|
|
lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_RED_SKULL)) {
|
|
return 1; // red skull!
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool Player::setFollowCreature(Creature* creature)
|
|
{
|
|
if (!Creature::setFollowCreature(creature)) {
|
|
setFollowCreature(nullptr);
|
|
setAttackedCreature(nullptr);
|
|
|
|
sendCancelMessage(RETURNVALUE_THEREISNOWAY);
|
|
sendCancelTarget();
|
|
stopWalk();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Player::setAttackedCreature(Creature* creature)
|
|
{
|
|
if (!Creature::setAttackedCreature(creature)) {
|
|
sendCancelTarget();
|
|
return false;
|
|
}
|
|
|
|
if (chaseMode == CHASEMODE_FOLLOW && creature) {
|
|
if (followCreature != creature) {
|
|
//chase opponent
|
|
setFollowCreature(creature);
|
|
}
|
|
} else if (followCreature) {
|
|
setFollowCreature(nullptr);
|
|
}
|
|
|
|
if (creature) {
|
|
g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID())));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Player::goToFollowCreature()
|
|
{
|
|
if (!walkTask) {
|
|
if ((OTSYS_TIME() - lastFailedFollow) < 2000) {
|
|
return;
|
|
}
|
|
|
|
Creature::goToFollowCreature();
|
|
|
|
if (followCreature && !hasFollowPath) {
|
|
lastFailedFollow = OTSYS_TIME();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const
|
|
{
|
|
Creature::getPathSearchParams(creature, fpp);
|
|
fpp.fullPathSearch = true;
|
|
}
|
|
|
|
void Player::doAttacking(uint32_t)
|
|
{
|
|
if (lastAttack == 0) {
|
|
lastAttack = OTSYS_TIME() - getAttackSpeed() - 1;
|
|
}
|
|
|
|
if (hasCondition(CONDITION_PACIFIED)) {
|
|
return;
|
|
}
|
|
|
|
if ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()) {
|
|
if (Combat::attack(this, attackedCreature)) {
|
|
earliestAttackTime = OTSYS_TIME() + 2000;
|
|
lastAttack = OTSYS_TIME();
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64_t Player::getGainedExperience(Creature* attacker) const
|
|
{
|
|
if (g_config.getBoolean(ConfigManager::EXPERIENCE_FROM_PLAYERS)) {
|
|
Player* attackerPlayer = attacker->getPlayer();
|
|
if (attackerPlayer && attackerPlayer != this && skillLoss && std::abs(static_cast<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(chaseMode_t mode)
|
|
{
|
|
chaseMode_t prevChaseMode = chaseMode;
|
|
chaseMode = mode;
|
|
|
|
if (prevChaseMode != chaseMode) {
|
|
if (chaseMode == CHASEMODE_FOLLOW) {
|
|
if (!followCreature && attackedCreature) {
|
|
//chase opponent
|
|
setFollowCreature(attackedCreature);
|
|
}
|
|
} else if (attackedCreature) {
|
|
setFollowCreature(nullptr);
|
|
cancelNextWalk = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::onWalkAborted()
|
|
{
|
|
setNextWalkActionTask(nullptr);
|
|
sendCancelWalk();
|
|
}
|
|
|
|
void Player::onWalkComplete()
|
|
{
|
|
if (walkTask) {
|
|
walkTaskEvent = g_scheduler.addEvent(walkTask);
|
|
walkTask = nullptr;
|
|
}
|
|
}
|
|
|
|
void Player::stopWalk()
|
|
{
|
|
cancelNextWalk = true;
|
|
}
|
|
|
|
void Player::getCreatureLight(LightInfo& light) const
|
|
{
|
|
if (internalLight.level > itemsLight.level) {
|
|
light = internalLight;
|
|
} else {
|
|
light = itemsLight;
|
|
}
|
|
}
|
|
|
|
void Player::updateItemsLight(bool internal /*=false*/)
|
|
{
|
|
LightInfo maxLight;
|
|
LightInfo curLight;
|
|
|
|
for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
|
|
Item* item = inventory[i];
|
|
if (item) {
|
|
item->getLight(curLight);
|
|
|
|
if (curLight.level > maxLight.level) {
|
|
maxLight = curLight;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (itemsLight.level != maxLight.level || itemsLight.color != maxLight.color) {
|
|
itemsLight = maxLight;
|
|
|
|
if (!internal) {
|
|
g_game.changeLight(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::onAddCondition(ConditionType_t type)
|
|
{
|
|
Creature::onAddCondition(type);
|
|
sendIcons();
|
|
}
|
|
|
|
void Player::onAddCombatCondition(ConditionType_t type)
|
|
{
|
|
switch (type) {
|
|
case CONDITION_POISON:
|
|
sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are poisoned.");
|
|
break;
|
|
|
|
case CONDITION_PARALYZE:
|
|
sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are paralyzed.");
|
|
break;
|
|
|
|
case CONDITION_DRUNK:
|
|
sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drunk.");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Player::onEndCondition(ConditionType_t type)
|
|
{
|
|
Creature::onEndCondition(type);
|
|
|
|
if (type == CONDITION_INFIGHT) {
|
|
onIdleStatus();
|
|
pzLocked = false;
|
|
clearAttacked();
|
|
|
|
if (getSkull() != SKULL_RED) {
|
|
setSkull(SKULL_NONE);
|
|
}
|
|
}
|
|
|
|
sendIcons();
|
|
}
|
|
|
|
void Player::onCombatRemoveCondition(Condition* condition)
|
|
{
|
|
//Creature::onCombatRemoveCondition(condition);
|
|
if (condition->getId() > 0) {
|
|
//Means the condition is from an item, id == slot
|
|
if (g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) {
|
|
Item* item = getInventoryItem(static_cast<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)
|
|
{
|
|
Creature::onAttackedCreature(target);
|
|
|
|
if (target->getZone() == ZONE_PVP) {
|
|
return;
|
|
}
|
|
|
|
if (target == this) {
|
|
addInFightTicks();
|
|
return;
|
|
}
|
|
|
|
if (hasFlag(PlayerFlag_NotGainInFight)) {
|
|
return;
|
|
}
|
|
|
|
Player* targetPlayer = target->getPlayer();
|
|
if (targetPlayer) {
|
|
if (!pzLocked && g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) {
|
|
pzLocked = true;
|
|
sendIcons();
|
|
}
|
|
|
|
if (!isPartner(targetPlayer)) {
|
|
if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) {
|
|
addAttacked(targetPlayer);
|
|
targetPlayer->sendCreatureSkull(this);
|
|
} else {
|
|
if (!targetPlayer->hasAttacked(this)) {
|
|
if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) {
|
|
addAttacked(targetPlayer);
|
|
if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) {
|
|
setSkull(SKULL_WHITE);
|
|
}
|
|
}
|
|
|
|
if (getSkull() == SKULL_NONE) {
|
|
targetPlayer->sendCreatureSkull(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
addInFightTicks();
|
|
}
|
|
|
|
void Player::onAttacked()
|
|
{
|
|
Creature::onAttacked();
|
|
|
|
addInFightTicks();
|
|
}
|
|
|
|
void Player::onIdleStatus()
|
|
{
|
|
Creature::onIdleStatus();
|
|
|
|
if (party) {
|
|
party->clearPlayerPoints(this);
|
|
}
|
|
}
|
|
|
|
void Player::onPlacedCreature()
|
|
{
|
|
//scripting event - onLogin
|
|
if (!g_creatureEvents->playerLogin(this)) {
|
|
kickPlayer(true);
|
|
}
|
|
}
|
|
|
|
void Player::onAttackedCreatureDrainHealth(Creature* target, int32_t points)
|
|
{
|
|
Creature::onAttackedCreatureDrainHealth(target, points);
|
|
|
|
if (target) {
|
|
if (party && !Combat::isPlayerCombat(target)) {
|
|
Monster* tmpMonster = target->getMonster();
|
|
if (tmpMonster && tmpMonster->isHostile()) {
|
|
//We have fulfilled a requirement for shared experience
|
|
party->updatePlayerTicks(this, points);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::onTargetCreatureGainHealth(Creature* target, int32_t points)
|
|
{
|
|
if (target && party) {
|
|
Player* tmpPlayer = nullptr;
|
|
|
|
if (target->getPlayer()) {
|
|
tmpPlayer = target->getPlayer();
|
|
} else if (Creature* targetMaster = target->getMaster()) {
|
|
if (Player* targetMasterPlayer = targetMaster->getPlayer()) {
|
|
tmpPlayer = targetMasterPlayer;
|
|
}
|
|
}
|
|
|
|
if (isPartner(tmpPlayer)) {
|
|
party->updatePlayerTicks(this, points);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/)
|
|
{
|
|
bool unjustified = false;
|
|
|
|
if (hasFlag(PlayerFlag_NotGenerateLoot)) {
|
|
target->setDropLoot(false);
|
|
}
|
|
|
|
Creature::onKilledCreature(target, lastHit);
|
|
|
|
if (Player* targetPlayer = target->getPlayer()) {
|
|
if (targetPlayer && targetPlayer->getZone() == ZONE_PVP) {
|
|
targetPlayer->setDropLoot(false);
|
|
targetPlayer->setLossSkill(false);
|
|
} else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) {
|
|
if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && targetPlayer != this) {
|
|
if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) {
|
|
unjustified = true;
|
|
addUnjustifiedDead(targetPlayer);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lastHit && hasCondition(CONDITION_INFIGHT)) {
|
|
pzLocked = true;
|
|
Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME) * 1000, 0);
|
|
addCondition(condition);
|
|
}
|
|
}
|
|
|
|
return unjustified;
|
|
}
|
|
|
|
void Player::gainExperience(uint64_t gainExp, Creature* source)
|
|
{
|
|
if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0 || staminaMinutes == 0) {
|
|
return;
|
|
}
|
|
|
|
addExperience(source, gainExp, true);
|
|
}
|
|
|
|
void Player::onGainExperience(uint64_t gainExp, Creature* target)
|
|
{
|
|
if (hasFlag(PlayerFlag_NotGainExperience)) {
|
|
return;
|
|
}
|
|
|
|
if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) {
|
|
party->shareExperience(gainExp, target);
|
|
//We will get a share of the experience through the sharing mechanism
|
|
return;
|
|
}
|
|
|
|
Creature::onGainExperience(gainExp, target);
|
|
gainExperience(gainExp, target);
|
|
}
|
|
|
|
void Player::onGainSharedExperience(uint64_t gainExp, Creature* source)
|
|
{
|
|
gainExperience(gainExp, source);
|
|
}
|
|
|
|
bool Player::isImmune(CombatType_t type) const
|
|
{
|
|
if (hasFlag(PlayerFlag_CannotBeAttacked)) {
|
|
return true;
|
|
}
|
|
return Creature::isImmune(type);
|
|
}
|
|
|
|
bool Player::isImmune(ConditionType_t type) const
|
|
{
|
|
if (hasFlag(PlayerFlag_CannotBeAttacked)) {
|
|
return true;
|
|
}
|
|
return Creature::isImmune(type);
|
|
}
|
|
|
|
bool Player::isAttackable() const
|
|
{
|
|
return !hasFlag(PlayerFlag_CannotBeAttacked);
|
|
}
|
|
|
|
bool Player::lastHitIsPlayer(Creature* lastHitCreature)
|
|
{
|
|
if (!lastHitCreature) {
|
|
return false;
|
|
}
|
|
|
|
if (lastHitCreature->getPlayer()) {
|
|
return true;
|
|
}
|
|
|
|
Creature* lastHitMaster = lastHitCreature->getMaster();
|
|
return lastHitMaster && lastHitMaster->getPlayer();
|
|
}
|
|
|
|
void Player::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/)
|
|
{
|
|
Creature::changeHealth(healthChange, sendHealthChange);
|
|
sendStats();
|
|
}
|
|
|
|
void Player::changeMana(int32_t manaChange)
|
|
{
|
|
if (!hasFlag(PlayerFlag_HasInfiniteMana)) {
|
|
if (manaChange > 0) {
|
|
mana += std::min<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) {
|
|
if (isInWar(player)) {
|
|
return SKULL_GREEN;
|
|
}
|
|
|
|
if (!player->getGuildWarList().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->id) != attackedSet.end();
|
|
}
|
|
|
|
void Player::addAttacked(const Player* attacked)
|
|
{
|
|
if (hasFlag(PlayerFlag_NotGainInFight) || !attacked || attacked == this) {
|
|
return;
|
|
}
|
|
|
|
attackedSet.insert(attacked->id);
|
|
}
|
|
|
|
void Player::removeAttacked(const Player* attacked)
|
|
{
|
|
if (!attacked || attacked == this) {
|
|
return;
|
|
}
|
|
|
|
auto it = attackedSet.find(attacked->guid);
|
|
if (it != attackedSet.end()) {
|
|
attackedSet.erase(it);
|
|
}
|
|
}
|
|
|
|
void Player::clearAttacked()
|
|
{
|
|
attackedSet.clear();
|
|
}
|
|
|
|
void Player::addUnjustifiedDead(const Player* attacked)
|
|
{
|
|
if (hasFlag(PlayerFlag_NotGainInFight) || attacked == this || g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) {
|
|
return;
|
|
}
|
|
|
|
// current unjustified kill!
|
|
murderTimeStamps.push_back(std::time(nullptr));
|
|
|
|
sendTextMessage(MESSAGE_STATUS_WARNING, "Warning! The murder of " + attacked->getName() + " was not justified.");
|
|
|
|
if (playerKillerEnd == 0) {
|
|
// white skull time, it only sets on first kill!
|
|
playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::WHITE_SKULL_TIME);
|
|
}
|
|
|
|
uint32_t murderResult = checkPlayerKilling();
|
|
if (murderResult >= 1) {
|
|
// red skull player
|
|
playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::RED_SKULL_TIME);
|
|
setSkull(SKULL_RED);
|
|
|
|
if (murderResult == 2) {
|
|
// banishment for too many unjustified kills
|
|
Database* db = Database::getInstance();
|
|
|
|
std::ostringstream ss;
|
|
ss << "INSERT INTO `account_bans` (`account_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES (";
|
|
ss << getAccount() << ", ";
|
|
ss << db->escapeString("Too many unjustified kills") << ", ";
|
|
ss << std::time(nullptr) << ", ";
|
|
ss << std::time(nullptr) + g_config.getNumber(ConfigManager::BAN_LENGTH) << ", ";
|
|
ss << "1);";
|
|
|
|
db->executeQuery(ss.str());
|
|
|
|
g_game.addMagicEffect(getPosition(), CONST_ME_GREEN_RINGS);
|
|
g_game.removeCreature(this);
|
|
disconnect();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::checkSkullTicks()
|
|
{
|
|
time_t today = std::time(nullptr);
|
|
|
|
if (!hasCondition(CONDITION_INFIGHT) && ((skull == SKULL_RED && today >= playerKillerEnd) || (skull == SKULL_WHITE))) {
|
|
setSkull(SKULL_NONE);
|
|
}
|
|
}
|
|
|
|
bool Player::isPromoted() const
|
|
{
|
|
uint16_t promotedVocation = g_vocations.getPromotedVocation(vocation->getId());
|
|
return promotedVocation == VOCATION_NONE && vocation->getId() != promotedVocation;
|
|
}
|
|
|
|
double Player::getLostPercent() const
|
|
{
|
|
int32_t blessingCount = std::bitset<5>(blessings).count();
|
|
|
|
int32_t deathLosePercent = g_config.getNumber(ConfigManager::DEATH_LOSE_PERCENT);
|
|
if (deathLosePercent != -1) {
|
|
if (isPromoted()) {
|
|
deathLosePercent -= 3;
|
|
}
|
|
|
|
deathLosePercent -= blessingCount;
|
|
return std::max<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;
|
|
}
|
|
|
|
if (isPromoted()) {
|
|
lossPercent *= 0.7;
|
|
}
|
|
|
|
return lossPercent * pow(0.92, blessingCount) / 100;
|
|
}
|
|
|
|
void Player::learnInstantSpell(const std::string& spellName)
|
|
{
|
|
if (!hasLearnedInstantSpell(spellName)) {
|
|
learnedInstantSpellList.push_front(spellName);
|
|
}
|
|
}
|
|
|
|
void Player::forgetInstantSpell(const std::string& spellName)
|
|
{
|
|
learnedInstantSpellList.remove(spellName);
|
|
}
|
|
|
|
bool Player::hasLearnedInstantSpell(const std::string& spellName) const
|
|
{
|
|
if (hasFlag(PlayerFlag_CannotUseSpells)) {
|
|
return false;
|
|
}
|
|
|
|
if (hasFlag(PlayerFlag_IgnoreSpellCheck)) {
|
|
return true;
|
|
}
|
|
|
|
for (const auto& learnedSpellName : learnedInstantSpellList) {
|
|
if (strcasecmp(learnedSpellName.c_str(), spellName.c_str()) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Player::isInWar(const Player* player) const
|
|
{
|
|
if (!player || !guild) {
|
|
return false;
|
|
}
|
|
|
|
const Guild* playerGuild = player->getGuild();
|
|
if (!playerGuild) {
|
|
return false;
|
|
}
|
|
|
|
return isInWarList(playerGuild->getId()) && player->isInWarList(guild->getId());
|
|
}
|
|
|
|
bool Player::isInWarList(uint32_t guildId) const
|
|
{
|
|
return std::find(guildWarList.begin(), guildWarList.end(), guildId) != guildWarList.end();
|
|
}
|
|
|
|
bool Player::isPremium() const
|
|
{
|
|
if (g_config.getBoolean(ConfigManager::FREE_PREMIUM) || hasFlag(PlayerFlag_IsAlwaysPremium)) {
|
|
return true;
|
|
}
|
|
|
|
return premiumDays > 0;
|
|
}
|
|
|
|
void Player::setPremiumDays(int32_t v)
|
|
{
|
|
premiumDays = v;
|
|
}
|
|
|
|
PartyShields_t Player::getPartyShield(const Player* player) const
|
|
{
|
|
if (!player) {
|
|
return SHIELD_NONE;
|
|
}
|
|
|
|
if (party) {
|
|
if (party->getLeader() == player) {
|
|
return SHIELD_YELLOW;
|
|
}
|
|
|
|
if (player->party == party) {
|
|
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) {
|
|
return false;
|
|
}
|
|
return party == player->party;
|
|
}
|
|
|
|
bool Player::isGuildMate(const Player* player) const
|
|
{
|
|
if (!player || !guild) {
|
|
return false;
|
|
}
|
|
return guild == player->guild;
|
|
}
|
|
|
|
void Player::sendPlayerPartyIcons(Player* player)
|
|
{
|
|
sendCreatureShield(player);
|
|
sendCreatureSkull(player);
|
|
}
|
|
|
|
bool Player::addPartyInvitation(Party* party)
|
|
{
|
|
auto it = std::find(invitePartyList.begin(), invitePartyList.end(), party);
|
|
if (it != invitePartyList.end()) {
|
|
return false;
|
|
}
|
|
|
|
invitePartyList.push_front(party);
|
|
return true;
|
|
}
|
|
|
|
void Player::removePartyInvitation(Party* party)
|
|
{
|
|
invitePartyList.remove(party);
|
|
}
|
|
|
|
void Player::clearPartyInvitations()
|
|
{
|
|
for (Party* invitingParty : invitePartyList) {
|
|
invitingParty->removeInvite(*this, false);
|
|
}
|
|
invitePartyList.clear();
|
|
}
|
|
|
|
void Player::sendClosePrivate(uint16_t channelId)
|
|
{
|
|
if (channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY) {
|
|
g_chat->removeUserFromChannel(*this, channelId);
|
|
}
|
|
|
|
if (client) {
|
|
client->sendClosePrivate(channelId);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
size_t Player::getMaxDepotItems() const
|
|
{
|
|
if (group->maxDepotItems != 0) {
|
|
return group->maxDepotItems;
|
|
} else if (isPremium()) {
|
|
return 2000;
|
|
}
|
|
|
|
return 1000;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|