/** * Tibia GIMUD Server - a free and open-source MMORPG server emulator * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "otpch.h" #include "pugicast.h" #include "items.h" #include "creature.h" #include "monster.h" #include "events.h" #include "game.h" #include "actions.h" #include "iologindata.h" #include "talkaction.h" #include "spells.h" #include "configmanager.h" #include "server.h" #include "globalevent.h" #include "bed.h" #include "scheduler.h" #include "databasetasks.h" #include "movement.h" extern ConfigManager g_config; extern Actions* g_actions; extern Chat* g_chat; extern TalkActions* g_talkActions; extern Spells* g_spells; extern Vocations g_vocations; extern GlobalEvents* g_globalEvents; extern Events* g_events; extern CreatureEvents* g_creatureEvents; extern Monsters g_monsters; extern MoveEvents* g_moveEvents; Game::~Game() { for (const auto& it : guilds) { delete it.second; } } void Game::start(ServiceManager* manager) { serviceManager = manager; g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, std::bind(&Game::checkLight, this))); g_scheduler.addEvent(createSchedulerTask(EVENT_CREATURE_THINK_INTERVAL, std::bind(&Game::checkCreatures, this, 0))); g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this))); } GameState_t Game::getGameState() const { return gameState; } void Game::setWorldType(WorldType_t type) { worldType = type; } void Game::setGameState(GameState_t newState) { if (gameState == GAME_STATE_SHUTDOWN) { return; //this cannot be stopped } if (gameState == newState) { return; } gameState = newState; switch (newState) { case GAME_STATE_INIT: { loadExperienceStages(); groups.load(); g_chat->load(); map.spawns.startup(); raids.loadFromXml(); raids.startup(); loadMotdNum(); loadPlayersRecord(); g_globalEvents->startup(); break; } case GAME_STATE_SHUTDOWN: { g_globalEvents->execute(GLOBALEVENT_SHUTDOWN); //kick all players that are still online auto it = players.begin(); while (it != players.end()) { it->second->kickPlayer(true); it = players.begin(); } saveMotdNum(); saveGameState(); g_dispatcher.addTask( createTask(std::bind(&Game::shutdown, this))); g_scheduler.stop(); g_databaseTasks.stop(); g_dispatcher.stop(); break; } case GAME_STATE_CLOSED: { /* kick all players without the CanAlwaysLogin flag */ auto it = players.begin(); while (it != players.end()) { if (!it->second->hasFlag(PlayerFlag_CanAlwaysLogin)) { it->second->kickPlayer(true); it = players.begin(); } else { ++it; } } saveGameState(); break; } default: break; } } void Game::saveGameState() { if (gameState == GAME_STATE_NORMAL) { setGameState(GAME_STATE_MAINTAIN); } std::cout << "Saving server..." << std::endl; for (const auto& it : players) { it.second->loginPosition = it.second->getPosition(); IOLoginData::savePlayer(it.second); } Map::save(); if (gameState == GAME_STATE_MAINTAIN) { setGameState(GAME_STATE_NORMAL); } } bool Game::loadMainMap(const std::string& filename) { Monster::despawnRange = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRANGE); Monster::despawnRadius = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRADIUS); return map.loadMap("data/world/" + filename + ".otbm", true); } void Game::loadMap(const std::string& path) { map.loadMap(path, false); } Cylinder* Game::internalGetCylinder(Player* player, const Position& pos) const { if (pos.x != 0xFFFF) { return map.getTile(pos); } //container if (pos.y & 0x40) { uint8_t from_cid = pos.y & 0x0F; return player->getContainerByID(from_cid); } //inventory return player; } Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index, uint32_t spriteId, stackPosType_t type) const { if (pos.x != 0xFFFF) { Tile* tile = map.getTile(pos); if (!tile) { return nullptr; } Thing* thing; switch (type) { case STACKPOS_LOOK: { return tile->getTopVisibleThing(player); } case STACKPOS_MOVE: { Item* item = tile->getTopDownItem(); if (item && item->isMoveable()) { thing = item; } else { thing = tile->getTopVisibleCreature(player); } break; } case STACKPOS_USEITEM: { thing = tile->getUseItem(index); break; } case STACKPOS_TOPDOWN_ITEM: { thing = tile->getTopDownItem(); break; } case STACKPOS_USETARGET: { thing = tile->getTopVisibleCreature(player); if (!thing) { thing = tile->getUseItem(index); } break; } default: { thing = nullptr; break; } } if (player && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { //do extra checks here if the thing is accessable if (thing && thing->getItem()) { if (tile->hasProperty(CONST_PROP_ISVERTICAL)) { if (player->getPosition().x + 1 == tile->getPosition().x) { thing = nullptr; } } else { // horizontal if (player->getPosition().y + 1 == tile->getPosition().y) { thing = nullptr; } } } } return thing; } //container if (pos.y & 0x40) { uint8_t fromCid = pos.y & 0x0F; Container* parentContainer = player->getContainerByID(fromCid); if (!parentContainer) { return nullptr; } uint8_t slot = pos.z; return parentContainer->getItemByIndex(player->getContainerIndex(fromCid) + slot); } else if (pos.y == 0 && pos.z == 0) { const ItemType& it = Item::items.getItemType(spriteId); if (it.id == 0) { return nullptr; } int32_t subType; if (it.isFluidContainer() && index < static_cast(sizeof(reverseFluidMap) / sizeof(uint8_t))) { subType = reverseFluidMap[index]; } else { subType = -1; } return findItemOfType(player, it.id, true, subType); } //inventory slots_t slot = static_cast(pos.y); return player->getInventoryItem(slot); } void Game::internalGetPosition(Item* item, Position& pos, uint8_t& stackpos) { pos.x = 0; pos.y = 0; pos.z = 0; stackpos = 0; Cylinder* topParent = item->getTopParent(); if (topParent) { if (Player* player = dynamic_cast(topParent)) { pos.x = 0xFFFF; Container* container = dynamic_cast(item->getParent()); if (container) { pos.y = static_cast(0x40) | static_cast(player->getContainerID(container)); pos.z = container->getThingIndex(item); stackpos = pos.z; } else { pos.y = player->getThingIndex(item); stackpos = pos.y; } } else if (Tile* tile = topParent->getTile()) { pos = tile->getPosition(); stackpos = tile->getThingIndex(item); } } } Creature* Game::getCreatureByID(uint32_t id) { if (id <= Player::playerAutoID) { return getPlayerByID(id); } else if (id <= Monster::monsterAutoID) { return getMonsterByID(id); } else if (id <= Npc::npcAutoID) { return getNpcByID(id); } return nullptr; } Monster* Game::getMonsterByID(uint32_t id) { if (id == 0) { return nullptr; } auto it = monsters.find(id); if (it == monsters.end()) { return nullptr; } return it->second; } Npc* Game::getNpcByID(uint32_t id) { if (id == 0) { return nullptr; } auto it = npcs.find(id); if (it == npcs.end()) { return nullptr; } return it->second; } Player* Game::getPlayerByID(uint32_t id) { if (id == 0) { return nullptr; } auto it = players.find(id); if (it == players.end()) { return nullptr; } return it->second; } Creature* Game::getCreatureByName(const std::string& s) { if (s.empty()) { return nullptr; } const std::string& lowerCaseName = asLowerCaseString(s); auto m_it = mappedPlayerNames.find(lowerCaseName); if (m_it != mappedPlayerNames.end()) { return m_it->second; } for (const auto& it : npcs) { if (lowerCaseName == asLowerCaseString(it.second->getName())) { return it.second; } } for (const auto& it : monsters) { if (lowerCaseName == asLowerCaseString(it.second->getName())) { return it.second; } } return nullptr; } Npc* Game::getNpcByName(const std::string& s) { if (s.empty()) { return nullptr; } const char* npcName = s.c_str(); for (const auto& it : npcs) { if (strcasecmp(npcName, it.second->getName().c_str()) == 0) { return it.second; } } return nullptr; } Player* Game::getPlayerByName(const std::string& s) { if (s.empty()) { return nullptr; } auto it = mappedPlayerNames.find(asLowerCaseString(s)); if (it == mappedPlayerNames.end()) { return nullptr; } return it->second; } Player* Game::getPlayerByGUID(const uint32_t& guid) { if (guid == 0) { return nullptr; } for (const auto& it : players) { if (guid == it.second->getGUID()) { return it.second; } } return nullptr; } ReturnValue Game::getPlayerByNameWildcard(const std::string& s, Player*& player) { size_t strlen = s.length(); if (strlen == 0 || strlen > 20) { return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE; } if (s.back() == '~') { const std::string& query = asLowerCaseString(s.substr(0, strlen - 1)); std::string result; ReturnValue ret = wildcardTree.findOne(query, result); if (ret != RETURNVALUE_NOERROR) { return ret; } player = getPlayerByName(result); } else { player = getPlayerByName(s); } if (!player) { return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE; } return RETURNVALUE_NOERROR; } Player* Game::getPlayerByAccount(uint32_t acc) { for (const auto& it : players) { if (it.second->getAccount() == acc) { return it.second; } } return nullptr; } bool Game::internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) { if (creature->getParent() != nullptr) { return false; } if (!map.placeCreature(pos, creature, extendedPos, forced)) { return false; } creature->incrementReferenceCounter(); creature->setID(); creature->addList(); return true; } bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) { if (!internalPlaceCreature(creature, pos, extendedPos, forced)) { return false; } SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true); for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true); } } for (Creature* spectator : spectators) { spectator->onCreatureAppear(creature, true); } creature->getParent()->postAddNotification(creature, nullptr, 0); addCreatureCheck(creature); creature->onPlacedCreature(); return true; } bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) { if (creature->isRemoved()) { return false; } Tile* tile = creature->getTile(); std::vector oldStackPosVector; SpectatorVec spectators; map.getSpectators(spectators, tile->getPosition(), true); for (Creature* spectator : spectators) { if (Player* player = spectator->getPlayer()) { oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1); } } tile->removeCreature(creature); const Position& tilePosition = tile->getPosition(); //send to client size_t i = 0; for (Creature* spectator : spectators) { if (Player* player = spectator->getPlayer()) { player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); } } //event method for (Creature* spectator : spectators) { spectator->onRemoveCreature(creature, isLogout); } creature->getParent()->postRemoveNotification(creature, nullptr, 0); creature->removeList(); creature->setRemoved(); ReleaseCreature(creature); removeCreatureCheck(creature); for (Creature* summon : creature->summons) { summon->setSkillLoss(false); removeCreature(summon); } return true; } void Game::playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) { Player* player = getPlayerByID(playerId); if (!player) { return; } uint8_t fromIndex = 0; if (fromPos.x == 0xFFFF) { if (fromPos.y & 0x40) { fromIndex = fromPos.z; } else { fromIndex = static_cast(fromPos.y); } } else { fromIndex = fromStackPos; } Thing* thing = internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE); if (!thing) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } if (Creature* movingCreature = thing->getCreature()) { Tile* tile = map.getTile(toPos); if (!tile) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } if (Position::areInRange<1, 1, 0>(movingCreature->getPosition(), player->getPosition())) { SchedulerTask* task = createSchedulerTask(1000, std::bind(&Game::playerMoveCreatureByID, this, player->getID(), movingCreature->getID(), movingCreature->getPosition(), tile->getPosition())); player->setNextActionTask(task); } else { playerMoveCreature(player, movingCreature, movingCreature->getPosition(), tile); } } else if (thing->getItem()) { Cylinder* toCylinder = internalGetCylinder(player, toPos); if (!toCylinder) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, thing->getItem(), toCylinder); } } void Game::playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, const Position& toPos) { Player* player = getPlayerByID(playerId); if (!player) { return; } Creature* movingCreature = getCreatureByID(movingCreatureId); if (!movingCreature) { return; } Tile* toTile = map.getTile(toPos); if (!toTile) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } playerMoveCreature(player, movingCreature, movingCreatureOrigPos, toTile); } void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile) { if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveCreatureByID, this, player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition())); player->setNextActionTask(task); return; } player->setNextActionTask(nullptr); if (g_config.getBoolean(ConfigManager::BLOCK_HEIGHT)) { if (toTile->getHeight() > 1) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } } if (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos, player->getPosition())) { //need to walk to the creature first before moving it std::forward_list listDir; if (player->getPathTo(movingCreatureOrigPos, listDir, 0, 1, true, true)) { g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(1500, std::bind(&Game::playerMoveCreatureByID, this, player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition())); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } return; } if ((!movingCreature->isPushable() && !player->hasFlag(PlayerFlag_CanPushAllCreatures)) || (movingCreature->isInGhostMode() && !player->isAccessPlayer())) { player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); return; } //check throw distance const Position& movingCreaturePos = movingCreature->getPosition(); const Position& toPos = toTile->getPosition(); if ((Position::getDistanceX(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceY(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceZ(movingCreaturePos, toPos) * 4 > movingCreature->getThrowRange())) { player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); return; } if (player != movingCreature) { if (toTile->hasFlag(TILESTATE_BLOCKPATH)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); return; } else if ((movingCreature->getZone() == ZONE_PROTECTION && !toTile->hasFlag(TILESTATE_PROTECTIONZONE)) || (movingCreature->getZone() == ZONE_NOPVP && !toTile->hasFlag(TILESTATE_NOPVPZONE))) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } else { if (CreatureVector* tileCreatures = toTile->getCreatures()) { for (Creature* tileCreature : *tileCreatures) { if (!tileCreature->isInGhostMode()) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); return; } } } Npc* movingNpc = movingCreature->getNpc(); if (movingNpc && !Spawns::isInZone(movingNpc->getMasterPos(), movingNpc->getMasterRadius(), toPos)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); return; } } } if (!g_events->eventPlayerOnMoveCreature(player, movingCreature, movingCreaturePos, toPos)) { return; } ReturnValue ret = internalMoveCreature(*movingCreature, *toTile); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); } } ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, uint32_t flags /*= 0*/) { creature->setLastPosition(creature->getPosition()); const Position& currentPos = creature->getPosition(); Position destPos = getNextPosition(direction, currentPos); bool diagonalMovement = (direction & DIRECTION_DIAGONAL_MASK) != 0; if (creature->getPlayer() && !diagonalMovement) { //try go up if (currentPos.z != 8 && creature->getTile()->hasHeight(3)) { Tile* tmpTile = map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { tmpTile = map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)) { destPos.z--; internalCreatureTurn(creature, DIRECTION_NORTH); } } } else { //try go down Tile* tmpTile = map.getTile(destPos.x, destPos.y, destPos.z); if (currentPos.z != 7 && (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)))) { tmpTile = map.getTile(destPos.x, destPos.y, destPos.z + 1); if (tmpTile && tmpTile->hasHeight(3)) { destPos.z++; internalCreatureTurn(creature, DIRECTION_SOUTH); } } } } ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; Tile* toTile = map.getTile(destPos); Tile* toPos = map.getTile(destPos.x, destPos.y, destPos.z); Tile* fromPos = map.getTile(currentPos.x, currentPos.y, currentPos.z); if (g_config.getBoolean(ConfigManager::BLOCK_HEIGHT)) { if (toTile) { if (currentPos.z > destPos.z && toPos->getHeight() > 1); // not possible else if ((((toPos->getHeight() - fromPos->getHeight()) < 2)) || (fromPos->hasHeight(3) && (currentPos.z == destPos.z)) || ((currentPos.z < destPos.z) && (toPos->hasHeight(3) && (fromPos->getHeight() < 2)))) ret = internalMoveCreature(*creature, *toTile, flags); } if (ret != RETURNVALUE_NOERROR) { if (Player* player = creature->getPlayer()) { player->sendCancelMessage(ret); player->sendCancelWalk(); } } return ret; } else { if (!toTile) { return RETURNVALUE_NOTPOSSIBLE; } return internalMoveCreature(*creature, *toTile, flags); } } ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags /*= 0*/) { //check if we can move the creature to the destination ReturnValue ret = toTile.queryAdd(0, creature, 1, flags); if (ret != RETURNVALUE_NOERROR) { return ret; } map.moveCreature(creature, toTile); if (creature.getParent() != &toTile) { return RETURNVALUE_NOERROR; } int32_t index = 0; Item* toItem = nullptr; Tile* subCylinder = nullptr; Tile* toCylinder = &toTile; Tile* fromCylinder = nullptr; uint32_t n = 0; while ((subCylinder = toCylinder->queryDestination(index, creature, &toItem, flags)) != toCylinder) { map.moveCreature(creature, *subCylinder); if (creature.getParent() != subCylinder) { //could happen if a script move the creature fromCylinder = nullptr; break; } fromCylinder = toCylinder; toCylinder = subCylinder; flags = 0; //to prevent infinite loop if (++n >= MAP_MAX_LAYERS) { break; } } if (fromCylinder) { const Position& fromPosition = fromCylinder->getPosition(); const Position& toPosition = toCylinder->getPosition(); if (fromPosition.z != toPosition.z && (fromPosition.x != toPosition.x || fromPosition.y != toPosition.y)) { Direction dir = getDirectionTo(fromPosition, toPosition); if ((dir & DIRECTION_DIAGONAL_MASK) == 0) { internalCreatureTurn(&creature, dir); } } } return RETURNVALUE_NOERROR; } void Game::playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) { Player* player = getPlayerByID(playerId); if (!player) { return; } playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, nullptr, nullptr); } void Game::playerMoveItem(Player* player, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder) { if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveItemByPlayerID, this, player->getID(), fromPos, spriteId, fromStackPos, toPos, count)); player->setNextActionTask(task); return; } player->setNextActionTask(nullptr); if (item == nullptr) { uint8_t fromIndex = 0; if (fromPos.x == 0xFFFF) { if (fromPos.y & 0x40) { fromIndex = fromPos.z; } else { fromIndex = static_cast(fromPos.y); } } else { fromIndex = fromStackPos; } Thing* thing = internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE); if (!thing || !thing->getItem()) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } item = thing->getItem(); } if ((item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } Cylinder* fromCylinder = internalGetCylinder(player, fromPos); if (fromCylinder == nullptr) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } if (toCylinder == nullptr) { toCylinder = internalGetCylinder(player, toPos); if (toCylinder == nullptr) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } } if (!item->isPushable()) { player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); return; } const Position& playerPos = player->getPosition(); const Position& mapFromPos = fromCylinder->getTile()->getPosition(); if (playerPos.z != mapFromPos.z) { player->sendCancelMessage(playerPos.z > mapFromPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); return; } if (!Position::areInRange<1, 1>(playerPos, mapFromPos)) { //need to walk to the item first before using it std::forward_list listDir; if (player->getPathTo(item->getPosition(), listDir, 0, 1, true, true)) { g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, player->getID(), fromPos, spriteId, fromStackPos, toPos, count)); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } return; } const Tile* toCylinderTile = toCylinder->getTile(); const Position& mapToPos = toCylinderTile->getPosition(); //hangable item specific code if (item->isHangable() && toCylinderTile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { //destination supports hangable objects so need to move there first bool vertical = toCylinderTile->hasProperty(CONST_PROP_ISVERTICAL); if (vertical) { if (playerPos.x + 1 == mapToPos.x) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } } else { // horizontal if (playerPos.y + 1 == mapToPos.y) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } } if (!Position::areInRange<1, 1, 0>(playerPos, mapToPos)) { Position walkPos = mapToPos; if (vertical) { walkPos.x++; } else { walkPos.y++; } Position itemPos = fromPos; uint8_t itemStackPos = fromStackPos; if (fromPos.x != 0xFFFF && Position::areInRange<1, 1>(mapFromPos, playerPos) && !Position::areInRange<1, 1, 0>(mapFromPos, walkPos)) { //need to pickup the item first Item* moveItem = nullptr; ReturnValue ret = internalMoveItem(fromCylinder, player, INDEX_WHEREEVER, item, count, &moveItem); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); return; } //changing the position since its now in the inventory of the player internalGetPosition(moveItem, itemPos, itemStackPos); } std::forward_list listDir; if (player->getPathTo(walkPos, listDir, 0, 0, true, true)) { g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, player->getID(), itemPos, spriteId, itemStackPos, toPos, count)); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } return; } } if ((Position::getDistanceX(playerPos, mapToPos) > item->getThrowRange()) || (Position::getDistanceY(playerPos, mapToPos) > item->getThrowRange()) || (Position::getDistanceZ(mapFromPos, mapToPos) * 4 > item->getThrowRange())) { player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); return; } if (!canThrowObjectTo(mapFromPos, mapToPos)) { player->sendCancelMessage(RETURNVALUE_CANNOTTHROW); return; } if (!g_events->eventPlayerOnMoveItem(player, item, count, fromPos, toPos, fromCylinder, toCylinder)) { return; } uint8_t toIndex = 0; if (toPos.x == 0xFFFF) { if (toPos.y & 0x40) { toIndex = toPos.z; } else { toIndex = static_cast(toPos.y); } } ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, item->isRune() ? item->getItemCount() : count, nullptr, 0, player); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); } else { g_events->eventPlayerOnItemMoved(player, item, item->isRune() ? item->getItemCount() : count, fromPos, toPos, fromCylinder, toCylinder); } } ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, Creature* actor/* = nullptr*/, Item* tradeItem/* = nullptr*/) { Item* toItem = nullptr; Cylinder* subCylinder; int floorN = 0; while ((subCylinder = toCylinder->queryDestination(index, *item, &toItem, flags)) != toCylinder) { toCylinder = subCylinder; flags = 0; //to prevent infinite loop if (++floorN >= MAP_MAX_LAYERS) { break; } } //destination is the same as the source? if (item == toItem) { return RETURNVALUE_NOERROR; //silently ignore move } //check if we can add this item ReturnValue ret = toCylinder->queryAdd(index, *item, count, flags, actor); if (ret == RETURNVALUE_NEEDEXCHANGE) { //check if we can add it to source cylinder ret = fromCylinder->queryAdd(fromCylinder->getThingIndex(item), *toItem, toItem->getItemCount(), 0); if (ret == RETURNVALUE_NOERROR) { //check how much we can move uint32_t maxExchangeQueryCount = 0; ReturnValue retExchangeMaxCount = fromCylinder->queryMaxCount(INDEX_WHEREEVER, *toItem, toItem->getItemCount(), maxExchangeQueryCount, 0); if (retExchangeMaxCount != RETURNVALUE_NOERROR && maxExchangeQueryCount == 0) { return retExchangeMaxCount; } if (toCylinder->queryRemove(*toItem, toItem->getItemCount(), flags) == RETURNVALUE_NOERROR) { int32_t oldToItemIndex = toCylinder->getThingIndex(toItem); toCylinder->removeThing(toItem, toItem->getItemCount()); fromCylinder->addThing(toItem); if (oldToItemIndex != -1) { toCylinder->postRemoveNotification(toItem, fromCylinder, oldToItemIndex); } int32_t newToItemIndex = fromCylinder->getThingIndex(toItem); if (newToItemIndex != -1) { fromCylinder->postAddNotification(toItem, toCylinder, newToItemIndex); } ret = toCylinder->queryAdd(index, *item, count, flags); toItem = nullptr; } } } if (ret != RETURNVALUE_NOERROR) { return ret; } //check how much we can move uint32_t maxQueryCount = 0; ReturnValue retMaxCount = toCylinder->queryMaxCount(index, *item, count, maxQueryCount, flags); if (retMaxCount != RETURNVALUE_NOERROR && maxQueryCount == 0) { return retMaxCount; } uint32_t m; if (item->isStackable()) { if (item->isRune()) { m = std::min(item->getItemCount(), maxQueryCount); } else { m = std::min(count, maxQueryCount); } } else { m = maxQueryCount; } Item* moveItem = item; //check if we can remove this item ret = fromCylinder->queryRemove(*item, m, flags); if (ret != RETURNVALUE_NOERROR) { return ret; } if (tradeItem) { if (toCylinder->getItem() == tradeItem) { return RETURNVALUE_NOTENOUGHROOM; } Cylinder* tmpCylinder = toCylinder->getParent(); while (tmpCylinder) { if (tmpCylinder->getItem() == tradeItem) { return RETURNVALUE_NOTENOUGHROOM; } tmpCylinder = tmpCylinder->getParent(); } } //remove the item int32_t itemIndex = fromCylinder->getThingIndex(item); Item* updateItem = nullptr; fromCylinder->removeThing(item, m); //update item(s) if (item->isStackable()) { uint32_t n; if (!item->isRune() && item->equals(toItem)) { n = std::min(100 - toItem->getItemCount(), m); toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); updateItem = toItem; } else { n = 0; } int32_t newCount = m - n; if (newCount > 0) { moveItem = item->clone(); moveItem->setItemCount(newCount); } else { moveItem = nullptr; } if (item->isRemoved()) { ReleaseItem(item); } } //add item if (moveItem /*m - n > 0*/) { toCylinder->addThing(index, moveItem); } if (itemIndex != -1) { fromCylinder->postRemoveNotification(item, toCylinder, itemIndex); } if (moveItem) { int32_t moveItemIndex = toCylinder->getThingIndex(moveItem); if (moveItemIndex != -1) { toCylinder->postAddNotification(moveItem, fromCylinder, moveItemIndex); } } if (updateItem) { int32_t updateItemIndex = toCylinder->getThingIndex(updateItem); if (updateItemIndex != -1) { toCylinder->postAddNotification(updateItem, fromCylinder, updateItemIndex); } } if (_moveItem) { if (moveItem) { *_moveItem = moveItem; } else { *_moveItem = item; } } //we could not move all, inform the player if (item->isStackable() && maxQueryCount < count) { return retMaxCount; } return ret; } ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index /*= INDEX_WHEREEVER*/, uint32_t flags/* = 0*/, bool test/* = false*/) { uint32_t remainderCount = 0; return internalAddItem(toCylinder, item, index, flags, test, remainderCount); } ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, uint32_t flags, bool test, uint32_t& remainderCount) { if (toCylinder == nullptr || item == nullptr) { return RETURNVALUE_NOTPOSSIBLE; } Cylinder* destCylinder = toCylinder; Item* toItem = nullptr; toCylinder = toCylinder->queryDestination(index, *item, &toItem, flags); //check if we can add this item ReturnValue ret = toCylinder->queryAdd(index, *item, item->getItemCount(), flags); if (ret != RETURNVALUE_NOERROR) { return ret; } /* Check if we can move add the whole amount, we do this by checking against the original cylinder, since the queryDestination can return a cylinder that might only hold a part of the full amount. */ uint32_t maxQueryCount = 0; ret = destCylinder->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), maxQueryCount, flags); if (ret != RETURNVALUE_NOERROR) { return ret; } if (test) { return RETURNVALUE_NOERROR; } if (item->isStackable() && !item->isRune() && item->equals(toItem)) { uint32_t m = std::min(item->getItemCount(), maxQueryCount); uint32_t n = std::min(100 - toItem->getItemCount(), m); toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); int32_t count = m - n; if (count > 0) { if (item->getItemCount() != count) { Item* remainderItem = item->clone(); remainderItem->setItemCount(count); if (internalAddItem(destCylinder, remainderItem, INDEX_WHEREEVER, flags, false) != RETURNVALUE_NOERROR) { ReleaseItem(remainderItem); remainderCount = count; } } else { toCylinder->addThing(index, item); int32_t itemIndex = toCylinder->getThingIndex(item); if (itemIndex != -1) { toCylinder->postAddNotification(item, nullptr, itemIndex); } } } else { //fully merged with toItem, item will be destroyed item->onRemoved(); ReleaseItem(item); int32_t itemIndex = toCylinder->getThingIndex(toItem); if (itemIndex != -1) { toCylinder->postAddNotification(toItem, nullptr, itemIndex); } } } else { toCylinder->addThing(index, item); int32_t itemIndex = toCylinder->getThingIndex(item); if (itemIndex != -1) { toCylinder->postAddNotification(item, nullptr, itemIndex); } } return RETURNVALUE_NOERROR; } ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool test /*= false*/, uint32_t flags /*= 0*/) { Cylinder* cylinder = item->getParent(); if (cylinder == nullptr) { return RETURNVALUE_NOTPOSSIBLE; } if (count == -1) { count = item->getItemCount(); } //check if we can remove this item ReturnValue ret = cylinder->queryRemove(*item, count, flags | FLAG_IGNORENOTMOVEABLE); if (ret != RETURNVALUE_NOERROR) { return ret; } if (!item->canRemove()) { return RETURNVALUE_NOTPOSSIBLE; } if (!test) { int32_t index = cylinder->getThingIndex(item); //remove the item cylinder->removeThing(item, count); if (item->isRemoved()) { ReleaseItem(item); } cylinder->postRemoveNotification(item, nullptr, index); } item->onRemoved(); return RETURNVALUE_NOERROR; } ReturnValue Game::internalPlayerAddItem(Player* player, Item* item, bool dropOnMap /*= true*/, slots_t slot /*= CONST_SLOT_WHEREEVER*/) { uint32_t remainderCount = 0; ReturnValue ret = internalAddItem(player, item, static_cast(slot), 0, false, remainderCount); if (remainderCount != 0) { Item* remainderItem = Item::CreateItem(item->getID(), remainderCount); ReturnValue remaindRet = internalAddItem(player->getTile(), remainderItem, INDEX_WHEREEVER, FLAG_NOLIMIT); if (remaindRet != RETURNVALUE_NOERROR) { ReleaseItem(remainderItem); } } if (ret != RETURNVALUE_NOERROR && dropOnMap) { ret = internalAddItem(player->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT); } return ret; } Item* Game::findItemOfType(Cylinder* cylinder, uint16_t itemId, bool depthSearch /*= true*/, int32_t subType /*= -1*/) const { if (cylinder == nullptr) { return nullptr; } std::vector containers; for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex(); i < j; ++i) { Thing* thing = cylinder->getThing(i); if (!thing) { continue; } Item* item = thing->getItem(); if (!item) { continue; } if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { return item; } if (depthSearch) { Container* container = item->getContainer(); if (container) { containers.push_back(container); } } } size_t i = 0; while (i < containers.size()) { Container* container = containers[i++]; for (Item* item : container->getItemList()) { if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { return item; } Container* subContainer = item->getContainer(); if (subContainer) { containers.push_back(subContainer); } } } return nullptr; } bool Game::removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) { if (cylinder == nullptr) { return false; } if (money == 0) { return true; } std::vector containers; std::multimap moneyMap; uint64_t moneyCount = 0; for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex(); i < j; ++i) { Thing* thing = cylinder->getThing(i); if (!thing) { continue; } Item* item = thing->getItem(); if (!item) { continue; } Container* container = item->getContainer(); if (container) { containers.push_back(container); } else { const uint32_t worth = item->getWorth(); if (worth != 0) { moneyCount += worth; moneyMap.emplace(worth, item); } } } size_t i = 0; while (i < containers.size()) { Container* container = containers[i++]; for (Item* item : container->getItemList()) { Container* tmpContainer = item->getContainer(); if (tmpContainer) { containers.push_back(tmpContainer); } else { const uint32_t worth = item->getWorth(); if (worth != 0) { moneyCount += worth; moneyMap.emplace(worth, item); } } } } if (moneyCount < money) { return false; } for (const auto& moneyEntry : moneyMap) { Item* item = moneyEntry.second; if (moneyEntry.first < money) { internalRemoveItem(item); money -= moneyEntry.first; } else if (moneyEntry.first > money) { const uint32_t worth = moneyEntry.first / item->getItemCount(); const uint32_t removeCount = std::ceil(money / static_cast(worth)); addMoney(cylinder, (worth * removeCount) - money, flags); internalRemoveItem(item, removeCount); break; } else { internalRemoveItem(item); break; } } return true; } void Game::addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) { if (money == 0) { return; } uint32_t crystalCoins = money / 10000; money -= crystalCoins * 10000; while (crystalCoins > 0) { const uint16_t count = std::min(100, crystalCoins); Item* remaindItem = Item::CreateItem(ITEM_CRYSTAL_COIN, count); ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); if (ret != RETURNVALUE_NOERROR) { internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); } crystalCoins -= count; } uint16_t platinumCoins = money / 100; if (platinumCoins != 0) { Item* remaindItem = Item::CreateItem(ITEM_PLATINUM_COIN, platinumCoins); ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); if (ret != RETURNVALUE_NOERROR) { internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); } money -= platinumCoins * 100; } if (money != 0) { Item* remaindItem = Item::CreateItem(ITEM_GOLD_COIN, money); ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); if (ret != RETURNVALUE_NOERROR) { internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); } } } Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) { if (item->getID() == newId && (newCount == -1 || (newCount == item->getSubType() && newCount != 0))) { //chargeless item placed on map = infinite return item; } Cylinder* cylinder = item->getParent(); if (cylinder == nullptr) { return nullptr; } int32_t itemIndex = cylinder->getThingIndex(item); if (itemIndex == -1) { return item; } if (!item->canTransform()) { return item; } const ItemType& newType = Item::items[newId]; if (newType.id == 0) { return item; } const ItemType& curType = Item::items[item->getID()]; if (curType.alwaysOnTop != newType.alwaysOnTop) { //This only occurs when you transform items on tiles from a downItem to a topItem (or vice versa) //Remove the old, and add the new cylinder->removeThing(item, item->getItemCount()); cylinder->postRemoveNotification(item, cylinder, itemIndex); item->setID(newId); if (newCount != -1) { item->setSubType(newCount); } cylinder->addThing(item); Cylinder* newParent = item->getParent(); if (newParent == nullptr) { ReleaseItem(item); return nullptr; } newParent->postAddNotification(item, cylinder, newParent->getThingIndex(item)); return item; } if (curType.type == newType.type) { //Both items has the same type so we can safely change id/subtype if (newCount == 0 && (item->isStackable() || item->hasAttribute(ITEM_ATTRIBUTE_CHARGES))) { if (item->isStackable()) { internalRemoveItem(item); return nullptr; } else { int32_t newItemId = newId; if (curType.id == newType.id) { newItemId = curType.decayTo; } if (newItemId < 0) { internalRemoveItem(item); return nullptr; } else if (newItemId != newId) { //Replacing the the old item with the new while maintaining the old position Item* newItem = Item::CreateItem(newItemId, 1); if (newItem == nullptr) { return nullptr; } cylinder->replaceThing(itemIndex, newItem); cylinder->postAddNotification(newItem, cylinder, itemIndex); item->setParent(nullptr); cylinder->postRemoveNotification(item, cylinder, itemIndex); ReleaseItem(item); return newItem; } else { return transformItem(item, newItemId); } } } else { cylinder->postRemoveNotification(item, cylinder, itemIndex); uint16_t itemId = item->getID(); int32_t count = item->getSubType(); if (curType.id != newType.id) { if (newType.group != curType.group) { item->setDefaultSubtype(); } itemId = newId; } if (newCount != -1 && newType.hasSubType()) { count = newCount; } cylinder->updateThing(item, itemId, count); cylinder->postAddNotification(item, cylinder, itemIndex); return item; } } //Replacing the the old item with the new while maintaining the old position Item* newItem; if (newCount == -1) { newItem = Item::CreateItem(newId); } else { newItem = Item::CreateItem(newId, newCount); } if (newItem == nullptr) { return nullptr; } cylinder->replaceThing(itemIndex, newItem); cylinder->postAddNotification(newItem, cylinder, itemIndex); item->setParent(nullptr); cylinder->postRemoveNotification(item, cylinder, itemIndex); ReleaseItem(item); return newItem; } ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pushMove/* = true*/, uint32_t flags /*= 0*/) { if (newPos == thing->getPosition()) { return RETURNVALUE_NOERROR; } else if (thing->isRemoved()) { return RETURNVALUE_NOTPOSSIBLE; } Tile* toTile = map.getTile(newPos); if (!toTile) { return RETURNVALUE_NOTPOSSIBLE; } if (Creature* creature = thing->getCreature()) { ReturnValue ret = toTile->queryAdd(0, *creature, 1, FLAG_NOLIMIT); if (ret != RETURNVALUE_NOERROR) { return ret; } Position fromPos = creature->getPosition(); if (Position::getOffsetX(fromPos, newPos) <= 0) { if (Position::getOffsetX(fromPos, newPos) < 0) { internalCreatureTurn(creature, DIRECTION_EAST); } else if (Position::getOffsetY(fromPos, newPos) < 0) { internalCreatureTurn(creature, DIRECTION_SOUTH); } else if (Position::getOffsetY(fromPos, newPos) > 0) { internalCreatureTurn(creature, DIRECTION_NORTH); } } else { internalCreatureTurn(creature, DIRECTION_WEST); } map.moveCreature(*creature, *toTile, !pushMove); return RETURNVALUE_NOERROR; } else if (Item* item = thing->getItem()) { return internalMoveItem(item->getParent(), toTile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr, flags); } return RETURNVALUE_NOTPOSSIBLE; } //Implementation of player invoked events void Game::playerMove(uint32_t playerId, Direction direction) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->resetIdleTime(); player->setNextWalkActionTask(nullptr); player->startAutoWalk(std::forward_list { direction }); } bool Game::playerBroadcastMessage(Player* player, const std::string& text) const { if (!player->hasFlag(PlayerFlag_CanBroadcast)) { return false; } std::cout << "> " << player->getName() << " broadcasted: \"" << text << "\"." << std::endl; for (const auto& it : players) { it.second->sendPrivateMessage(player, TALKTYPE_BROADCAST, text); } return true; } void Game::playerCreatePrivateChannel(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player || !player->isPremium()) { return; } ChatChannel* channel = g_chat->createChannel(*player, CHANNEL_PRIVATE); if (!channel || !channel->addUser(*player)) { return; } player->sendCreatePrivateChannel(channel->getId(), channel->getName()); } void Game::playerChannelInvite(uint32_t playerId, const std::string& name) { Player* player = getPlayerByID(playerId); if (!player) { return; } PrivateChatChannel* channel = g_chat->getPrivateChannel(*player); if (!channel) { return; } Player* invitePlayer = getPlayerByName(name); if (!invitePlayer) { return; } if (player == invitePlayer) { return; } channel->invitePlayer(*player, *invitePlayer); } void Game::playerChannelExclude(uint32_t playerId, const std::string& name) { Player* player = getPlayerByID(playerId); if (!player) { return; } PrivateChatChannel* channel = g_chat->getPrivateChannel(*player); if (!channel) { return; } Player* excludePlayer = getPlayerByName(name); if (!excludePlayer) { return; } if (player == excludePlayer) { return; } channel->excludePlayer(*player, *excludePlayer); } void Game::playerRequestChannels(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->sendChannelsDialog(); } void Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) { Player* player = getPlayerByID(playerId); if (!player) { return; } ChatChannel* channel = g_chat->addUserToChannel(*player, channelId); if (!channel) { return; } if (channel->getId() == CHANNEL_RULE_REP) { player->sendRuleViolationsChannel(channel->getId()); } else { player->sendChannel(channel->getId(), channel->getName()); } } void Game::playerCloseChannel(uint32_t playerId, uint16_t channelId) { Player* player = getPlayerByID(playerId); if (!player) { return; } g_chat->removeUserFromChannel(*player, channelId); } void Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver) { Player* player = getPlayerByID(playerId); if (!player) { return; } if (!IOLoginData::formatPlayerName(receiver)) { player->sendCancelMessage("A player with this name does not exist."); return; } if (player->getName() == receiver) { player->sendCancelMessage("You cannot set up a private message channel with yourself."); return; } player->sendOpenPrivateChannel(receiver); } void Game::playerReceivePing(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->receivePing(); } void Game::playerReceivePingBack(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->sendPingBack(); } void Game::playerAutoWalk(uint32_t playerId, const std::forward_list& listDir) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->resetIdleTime(); player->setNextWalkTask(nullptr); player->startAutoWalk(listDir); } void Game::playerStopAutoWalk(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->stopWalk(); } void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint16_t fromSpriteId, const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId) { Player* player = getPlayerByID(playerId); if (!player) { return; } bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0); if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) { return; } Thing* thing = internalGetThing(player, fromPos, fromStackPos, fromSpriteId, STACKPOS_USEITEM); if (!thing) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } Item* item = thing->getItem(); if (!item || (item->isDisguised() && item->getDisguiseId() != fromSpriteId) || (!item->isDisguised() && item->getID() != fromSpriteId)) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } Position walkToPos = fromPos; ReturnValue ret = g_actions->canUse(player, fromPos); if (ret == RETURNVALUE_NOERROR) { ret = g_actions->canUse(player, toPos, item); if (ret == RETURNVALUE_TOOFARAWAY) { walkToPos = toPos; } } if (ret != RETURNVALUE_NOERROR) { if (ret == RETURNVALUE_TOOFARAWAY) { Position itemPos = fromPos; uint8_t itemStackPos = fromStackPos; if (fromPos.x != 0xFFFF && toPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && !Position::areInRange<1, 1, 0>(fromPos, toPos)) { Item* moveItem = nullptr; ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); return; } //changing the position since its now in the inventory of the player internalGetPosition(moveItem, itemPos, itemStackPos); } std::forward_list listDir; if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItemEx, this, playerId, itemPos, itemStackPos, fromSpriteId, toPos, toStackPos, toSpriteId)); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } return; } player->sendCancelMessage(ret); return; } if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItemEx, this, playerId, fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId)); player->setNextActionTask(task); return; } player->resetIdleTime(); player->setNextActionTask(nullptr); g_actions->useItemEx(player, fromPos, toPos, toStackPos, item, isHotkey); } void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, uint8_t index, uint16_t spriteId) { Player* player = getPlayerByID(playerId); if (!player) { return; } bool isHotkey = (pos.x == 0xFFFF && pos.y == 0 && pos.z == 0); if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) { return; } Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USEITEM); if (!thing) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } Item* item = thing->getItem(); if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } ReturnValue ret = g_actions->canUse(player, pos); if (ret != RETURNVALUE_NOERROR) { if (ret == RETURNVALUE_TOOFARAWAY) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItem, this, playerId, pos, stackPos, index, spriteId)); player->setNextWalkActionTask(task); return; } ret = RETURNVALUE_THEREISNOWAY; } player->sendCancelMessage(ret); return; } if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItem, this, playerId, pos, stackPos, index, spriteId)); player->setNextActionTask(task); return; } player->resetIdleTime(); player->setNextActionTask(nullptr); g_actions->useItem(player, pos, index, item, isHotkey); } void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId) { Player* player = getPlayerByID(playerId); if (!player) { return; } Creature* creature = getCreatureByID(creatureId); if (!creature) { return; } if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) { return; } bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0); if (!g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) { if (creature->getPlayer() || isHotkey) { player->sendCancelMessage(RETURNVALUE_DIRECTPLAYERSHOOT); return; } } Thing* thing = internalGetThing(player, fromPos, fromStackPos, spriteId, STACKPOS_USEITEM); if (!thing) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } Item* item = thing->getItem(); if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } Position toPos = creature->getPosition(); Position walkToPos = fromPos; ReturnValue ret = g_actions->canUse(player, fromPos); if (ret == RETURNVALUE_NOERROR) { ret = g_actions->canUse(player, toPos, item); if (ret == RETURNVALUE_TOOFARAWAY) { walkToPos = toPos; } } if (ret != RETURNVALUE_NOERROR) { if (ret == RETURNVALUE_TOOFARAWAY) { Position itemPos = fromPos; uint8_t itemStackPos = fromStackPos; if (fromPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && !Position::areInRange<1, 1, 0>(fromPos, toPos)) { Item* moveItem = nullptr; ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); return; } //changing the position since its now in the inventory of the player internalGetPosition(moveItem, itemPos, itemStackPos); } std::forward_list listDir; if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseWithCreature, this, playerId, itemPos, itemStackPos, creatureId, spriteId)); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } return; } player->sendCancelMessage(ret); return; } if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseWithCreature, this, playerId, fromPos, fromStackPos, creatureId, spriteId)); player->setNextActionTask(task); return; } player->resetIdleTime(); player->setNextActionTask(nullptr); g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->getThingIndex(creature), item, isHotkey, creature); } void Game::playerCloseContainer(uint32_t playerId, uint8_t cid) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->closeContainer(cid); player->sendCloseContainer(cid); } void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) { Player* player = getPlayerByID(playerId); if (!player) { return; } Container* container = player->getContainerByID(cid); if (!container) { return; } Container* parentContainer = dynamic_cast(container->getRealParent()); if (!parentContainer) { return; } player->addContainer(cid, parentContainer); player->sendContainer(cid, parentContainer, parentContainer->hasParent(), player->getContainerIndex(cid)); } void Game::playerUpdateContainer(uint32_t playerId, uint8_t cid) { Player* player = getPlayerByID(playerId); if (!player) { return; } Container* container = player->getContainerByID(cid); if (!container) { return; } player->sendContainer(cid, container, container->hasParent(), player->getContainerIndex(cid)); } void Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId) { Player* player = getPlayerByID(playerId); if (!player) { return; } Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM); if (!thing) { return; } Item* item = thing->getItem(); if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || !item->isRotatable() || (!item->isDisguised() && item->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRotateItem, this, playerId, pos, stackPos, spriteId)); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } return; } uint16_t newId = Item::items[item->getID()].rotateTo; if (newId != 0) { transformItem(item, newId); } } void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text) { Player* player = getPlayerByID(playerId); if (!player) { return; } uint16_t maxTextLength = 0; uint32_t internalWindowTextId = 0; Item* writeItem = player->getWriteItem(internalWindowTextId, maxTextLength); if (text.length() > maxTextLength || windowTextId != internalWindowTextId) { return; } if (!writeItem || writeItem->isRemoved()) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } Cylinder* topParent = writeItem->getTopParent(); Player* owner = dynamic_cast(topParent); if (owner && owner != player) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } if (!Position::areInRange<1, 1, 0>(writeItem->getPosition(), player->getPosition())) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } if (!text.empty()) { if (writeItem->getText() != text) { writeItem->setText(text); writeItem->setWriter(player->getName()); writeItem->setDate(time(nullptr)); } } else { writeItem->resetText(); writeItem->resetWriter(); writeItem->resetDate(); } uint16_t newId = Item::items[writeItem->getID()].writeOnceItemId; if (newId != 0) { transformItem(writeItem, newId); } player->setWriteItem(nullptr); } void Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index) { Player* player = getPlayerByID(playerId); if (!player) { return; } Container* container = player->getContainerByID(containerId); if (!container) { return; } if ((index % container->capacity()) != 0 || index >= container->size()) { return; } player->setContainerIndex(containerId, index); player->sendContainer(containerId, container, container->hasParent(), index); } void Game::playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text) { Player* player = getPlayerByID(playerId); if (!player) { return; } uint32_t internalWindowTextId; uint32_t internalListId; House* house = player->getEditHouse(internalWindowTextId, internalListId); if (house && house->canEditAccessList(internalListId, player) && internalWindowTextId == windowTextId && listId == 0) { house->setAccessList(internalListId, text); } player->setEditHouse(nullptr); } void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, uint32_t tradePlayerId, uint16_t spriteId) { Player* player = getPlayerByID(playerId); if (!player) { return; } Player* tradePartner = getPlayerByID(tradePlayerId); if (!tradePartner || tradePartner == player) { player->sendTextMessage(MESSAGE_INFO_DESCR, "Sorry, not possible."); return; } if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player->getPosition())) { std::ostringstream ss; ss << tradePartner->getName() << " tells you to move closer."; player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); return; } if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); return; } Thing* tradeThing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM); if (!tradeThing) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } Item* tradeItem = tradeThing->getItem(); if (!tradeItem->isPickupable() || (tradeItem->isDisguised() && tradeItem->getDisguiseId() != spriteId) || (!tradeItem->isDisguised() && tradeItem->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } const Position& playerPosition = player->getPosition(); const Position& tradeItemPosition = tradeItem->getPosition(); if (playerPosition.z != tradeItemPosition.z) { player->sendCancelMessage(playerPosition.z > tradeItemPosition.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); return; } if (!Position::areInRange<1, 1>(tradeItemPosition, playerPosition)) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRequestTrade, this, playerId, pos, stackPos, tradePlayerId, spriteId)); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } return; } Container* tradeItemContainer = tradeItem->getContainer(); if (tradeItemContainer) { for (const auto& it : tradeItems) { Item* item = it.first; if (tradeItem == item) { player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); return; } if (tradeItemContainer->isHoldingItem(item)) { player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); return; } Container* container = item->getContainer(); if (container && container->isHoldingItem(tradeItem)) { player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); return; } } } else { for (const auto& it : tradeItems) { Item* item = it.first; if (tradeItem == item) { player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); return; } Container* container = item->getContainer(); if (container && container->isHoldingItem(tradeItem)) { player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); return; } } } Container* tradeContainer = tradeItem->getContainer(); if (tradeContainer && tradeContainer->getItemHoldingCount() + 1 > 100) { player->sendTextMessage(MESSAGE_INFO_DESCR, "You can not trade more than 100 items."); return; } if (!g_events->eventPlayerOnTradeRequest(player, tradePartner, tradeItem)) { return; } internalStartTrade(player, tradePartner, tradeItem); } bool Game::internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem) { if (player->tradeState != TRADE_NONE && !(player->tradeState == TRADE_ACKNOWLEDGE && player->tradePartner == tradePartner)) { player->sendCancelMessage(RETURNVALUE_YOUAREALREADYTRADING); return false; } else if (tradePartner->tradeState != TRADE_NONE && tradePartner->tradePartner != player) { player->sendCancelMessage(RETURNVALUE_THISPLAYERISALREADYTRADING); return false; } player->tradePartner = tradePartner; player->tradeItem = tradeItem; player->tradeState = TRADE_INITIATED; tradeItem->incrementReferenceCounter(); tradeItems[tradeItem] = player->getID(); player->sendTradeItemRequest(player->getName(), tradeItem, true); if (tradePartner->tradeState == TRADE_NONE) { std::ostringstream ss; ss << player->getName() << " wants to trade with you."; tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); tradePartner->tradeState = TRADE_ACKNOWLEDGE; tradePartner->tradePartner = player; } else { Item* counterOfferItem = tradePartner->tradeItem; player->sendTradeItemRequest(tradePartner->getName(), counterOfferItem, false); tradePartner->sendTradeItemRequest(player->getName(), tradeItem, false); } return true; } void Game::playerAcceptTrade(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } if (!(player->getTradeState() == TRADE_ACKNOWLEDGE || player->getTradeState() == TRADE_INITIATED)) { return; } Player* tradePartner = player->tradePartner; if (!tradePartner) { return; } if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); return; } player->setTradeState(TRADE_ACCEPT); if (tradePartner->getTradeState() == TRADE_ACCEPT) { Item* playerTradeItem = player->tradeItem; Item* partnerTradeItem = tradePartner->tradeItem; if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, playerTradeItem, partnerTradeItem)) { internalCloseTrade(player); return; } player->setTradeState(TRADE_TRANSFER); tradePartner->setTradeState(TRADE_TRANSFER); auto it = tradeItems.find(playerTradeItem); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); } it = tradeItems.find(partnerTradeItem); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); } bool isSuccess = false; ReturnValue tradePartnerRet = internalAddItem(tradePartner, playerTradeItem, INDEX_WHEREEVER, 0, true); ReturnValue playerRet = internalAddItem(player, partnerTradeItem, INDEX_WHEREEVER, 0, true); if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { tradePartnerRet = internalRemoveItem(playerTradeItem, playerTradeItem->getItemCount(), true); playerRet = internalRemoveItem(partnerTradeItem, partnerTradeItem->getItemCount(), true); if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { Cylinder* cylinder1 = playerTradeItem->getParent(); Cylinder* cylinder2 = partnerTradeItem->getParent(); uint32_t count1 = playerTradeItem->getItemCount(); uint32_t count2 = partnerTradeItem->getItemCount(); tradePartnerRet = internalMoveItem(cylinder1, tradePartner, INDEX_WHEREEVER, playerTradeItem, count1, nullptr, FLAG_IGNOREAUTOSTACK, nullptr, partnerTradeItem); if (tradePartnerRet == RETURNVALUE_NOERROR) { internalMoveItem(cylinder2, player, INDEX_WHEREEVER, partnerTradeItem, count2, nullptr, FLAG_IGNOREAUTOSTACK); playerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); partnerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, player); isSuccess = true; } } } if (!isSuccess) { std::string errorDescription; if (tradePartner->tradeItem) { errorDescription = getTradeErrorDescription(tradePartnerRet, playerTradeItem); tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); } if (player->tradeItem) { errorDescription = getTradeErrorDescription(playerRet, partnerTradeItem); player->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); } } player->setTradeState(TRADE_NONE); player->tradeItem = nullptr; player->tradePartner = nullptr; player->sendTradeClose(); tradePartner->setTradeState(TRADE_NONE); tradePartner->tradeItem = nullptr; tradePartner->tradePartner = nullptr; tradePartner->sendTradeClose(); } } std::string Game::getTradeErrorDescription(ReturnValue ret, Item* item) { if (item) { if (ret == RETURNVALUE_NOTENOUGHCAPACITY) { std::ostringstream ss; ss << "You do not have enough capacity to carry"; if (item->isStackable() && item->getItemCount() > 1) { ss << " these objects."; } else { ss << " this object."; } ss << std::endl << ' ' << item->getWeightDescription(); return ss.str(); } else if (ret == RETURNVALUE_NOTENOUGHROOM || ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) { std::ostringstream ss; ss << "You do not have enough room to carry"; if (item->isStackable() && item->getItemCount() > 1) { ss << " these objects."; } else { ss << " this object."; } return ss.str(); } } return "Trade could not be completed."; } void Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index) { Player* player = getPlayerByID(playerId); if (!player) { return; } Player* tradePartner = player->tradePartner; if (!tradePartner) { return; } Item* tradeItem; if (lookAtCounterOffer) { tradeItem = tradePartner->getTradeItem(); } else { tradeItem = player->getTradeItem(); } if (!tradeItem) { return; } const Position& playerPosition = player->getPosition(); const Position& tradeItemPosition = tradeItem->getPosition(); int32_t lookDistance = std::max(Position::getDistanceX(playerPosition, tradeItemPosition), Position::getDistanceY(playerPosition, tradeItemPosition)); if (index == 0) { g_events->eventPlayerOnLookInTrade(player, tradePartner, tradeItem, lookDistance); return; } Container* tradeContainer = tradeItem->getContainer(); if (!tradeContainer) { return; } std::vector containers {tradeContainer}; size_t i = 0; while (i < containers.size()) { const Container* container = containers[i++]; for (Item* item : container->getItemList()) { Container* tmpContainer = item->getContainer(); if (tmpContainer) { containers.push_back(tmpContainer); } if (--index == 0) { g_events->eventPlayerOnLookInTrade(player, tradePartner, item, lookDistance); return; } } } } void Game::playerCloseTrade(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } internalCloseTrade(player); } void Game::internalCloseTrade(Player* player) { Player* tradePartner = player->tradePartner; if ((tradePartner && tradePartner->getTradeState() == TRADE_TRANSFER) || player->getTradeState() == TRADE_TRANSFER) { return; } if (player->getTradeItem()) { auto it = tradeItems.find(player->getTradeItem()); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); } player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); player->tradeItem = nullptr; } player->setTradeState(TRADE_NONE); player->tradePartner = nullptr; player->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); player->sendTradeClose(); if (tradePartner) { if (tradePartner->getTradeItem()) { auto it = tradeItems.find(tradePartner->getTradeItem()); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); } tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); tradePartner->tradeItem = nullptr; } tradePartner->setTradeState(TRADE_NONE); tradePartner->tradePartner = nullptr; tradePartner->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); tradePartner->sendTradeClose(); } } void Game::playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos) { Player* player = getPlayerByID(playerId); if (!player) { return; } Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_LOOK); if (!thing) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } Position thingPos = thing->getPosition(); if (!player->canSee(thingPos)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } Position playerPos = player->getPosition(); int32_t lookDistance; if (thing != player) { lookDistance = std::max(Position::getDistanceX(playerPos, thingPos), Position::getDistanceY(playerPos, thingPos)); if (playerPos.z != thingPos.z) { lookDistance += 15; } } else { lookDistance = -1; } g_events->eventPlayerOnLook(player, pos, thing, stackPos, lookDistance); } void Game::playerLookInBattleList(uint32_t playerId, uint32_t creatureId) { Player* player = getPlayerByID(playerId); if (!player) { return; } Creature* creature = getCreatureByID(creatureId); if (!creature) { return; } if (!player->canSeeCreature(creature)) { return; } const Position& creaturePos = creature->getPosition(); if (!player->canSee(creaturePos)) { return; } int32_t lookDistance; if (creature != player) { const Position& playerPos = player->getPosition(); lookDistance = std::max(Position::getDistanceX(playerPos, creaturePos), Position::getDistanceY(playerPos, creaturePos)); if (playerPos.z != creaturePos.z) { lookDistance += 15; } } else { lookDistance = -1; } g_events->eventPlayerOnLookInBattleList(player, creature, lookDistance); } void Game::playerCancelAttackAndFollow(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } playerSetAttackedCreature(playerId, 0); playerFollowCreature(playerId, 0); player->stopWalk(); } void Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) { Player* player = getPlayerByID(playerId); if (!player) { return; } if (player->getAttackedCreature() && creatureId == 0) { player->setAttackedCreature(nullptr); player->sendCancelTarget(); return; } Creature* attackCreature = getCreatureByID(creatureId); if (!attackCreature) { player->setAttackedCreature(nullptr); player->sendCancelTarget(); return; } ReturnValue ret = Combat::canTargetCreature(player, attackCreature); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); player->sendCancelTarget(); player->setAttackedCreature(nullptr); return; } player->setAttackedCreature(attackCreature); g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID()))); } void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->setAttackedCreature(nullptr); g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID()))); player->setFollowCreature(getCreatureByID(creatureId)); } void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, bool secureMode) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->setFightMode(fightMode); player->setChaseMode(chaseMode); player->setSecureMode(secureMode); } void Game::playerRequestAddVip(uint32_t playerId, const std::string& name) { if (name.length() > 20) { return; } Player* player = getPlayerByID(playerId); if (!player) { return; } Player* vipPlayer = getPlayerByName(name); if (!vipPlayer) { uint32_t guid; bool specialVip; std::string formattedName = name; if (!IOLoginData::getGuidByNameEx(guid, specialVip, formattedName)) { player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name does not exist."); return; } if (specialVip && !player->hasFlag(PlayerFlag_SpecialVIP)) { player->sendTextMessage(MESSAGE_STATUS_SMALL, "You can not add this player."); return; } player->addVIP(guid, formattedName, VIPSTATUS_OFFLINE); } else { if (vipPlayer->hasFlag(PlayerFlag_SpecialVIP) && !player->hasFlag(PlayerFlag_SpecialVIP)) { player->sendTextMessage(MESSAGE_STATUS_SMALL, "You can not add this player."); return; } if (!vipPlayer->isInGhostMode() || player->isAccessPlayer()) { player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_ONLINE); } else { player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_OFFLINE); } } } void Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->removeVIP(guid); } void Game::playerTurn(uint32_t playerId, Direction dir) { Player* player = getPlayerByID(playerId); if (!player) { return; } if (!g_events->eventPlayerOnTurn(player, dir)) { return; } player->resetIdleTime(); internalCreatureTurn(player, dir); } void Game::playerRequestOutfit(uint32_t playerId) { if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { return; } Player* player = getPlayerByID(playerId); if (!player) { return; } player->sendOutfitWindow(); } void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) { if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { return; } Player* player = getPlayerByID(playerId); if (!player) { return; } if (player->canWear(outfit.lookType, outfit.lookAddons)) { player->defaultOutfit = outfit; if (player->hasCondition(CONDITION_OUTFIT)) { return; } internalCreatureChangeOutfit(player, outfit); } } void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, const std::string& receiver, const std::string& text) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->resetIdleTime(); if (playerSaySpell(player, type, text)) { return; } uint32_t muteTime = player->isMuted(); if (muteTime > 0) { std::ostringstream ss; ss << "You are still muted for " << muteTime << " seconds."; player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); return; } if (!text.empty() && text.front() == '/' && player->isAccessPlayer()) { return; } player->removeMessageBuffer(); switch (type) { case TALKTYPE_SAY: internalCreatureSay(player, TALKTYPE_SAY, text, false); break; case TALKTYPE_WHISPER: playerWhisper(player, text); break; case TALKTYPE_YELL: playerYell(player, text); break; case TALKTYPE_PRIVATE: case TALKTYPE_PRIVATE_RED: case TALKTYPE_RVR_ANSWER: playerSpeakTo(player, type, receiver, text); break; case TALKTYPE_CHANNEL_O: case TALKTYPE_CHANNEL_Y: case TALKTYPE_CHANNEL_R1: case TALKTYPE_CHANNEL_R2: if (channelId == CHANNEL_RULE_REP) { playerSay(playerId, 0, TALKTYPE_SAY, receiver, text); } else { g_chat->talkToChannel(*player, type, text, channelId); } break; case TALKTYPE_BROADCAST: playerBroadcastMessage(player, text); break; case TALKTYPE_RVR_CHANNEL: playerReportRuleViolationReport(player, text); break; case TALKTYPE_RVR_CONTINUE: playerContinueRuleViolationReport(player, text); break; default: break; } } bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& text) { std::string words = text; TalkActionResult_t result = g_talkActions->playerSaySpell(player, type, words); if (result == TALKACTION_BREAK) { return true; } result = g_spells->playerSaySpell(player, words); if (result == TALKACTION_BREAK) { return internalCreatureSay(player, TALKTYPE_SAY, text, false); } else if (result == TALKACTION_FAILED) { return true; } return false; } void Game::playerWhisper(Player* player, const std::string& text) { SpectatorVec spectators; map.getSpectators(spectators, player->getPosition(), false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); //send to client for (Creature* spectator : spectators) { if (Player* spectatorPlayer = spectator->getPlayer()) { if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) { spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps"); } else { spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, text); } } } //event method for (Creature* spectator : spectators) { spectator->onCreatureSay(player, TALKTYPE_WHISPER, text); } } bool Game::playerYell(Player* player, const std::string& text) { if (player->getLevel() == 1) { player->sendTextMessage(MESSAGE_STATUS_SMALL, "You may not yell as long as you are on level 1."); return false; } if (player->hasCondition(CONDITION_YELLTICKS)) { player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); return false; } if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_YELLTICKS, 30000, 0); player->addCondition(condition); } internalCreatureSay(player, TALKTYPE_YELL, asUpperCaseString(text), false); return true; } bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, const std::string& text) { Player* toPlayer = getPlayerByName(receiver); if (!toPlayer) { player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name is not online."); return false; } if (type == TALKTYPE_PRIVATE_RED && (!player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER)) { type = TALKTYPE_PRIVATE; } toPlayer->sendPrivateMessage(player, type, text); toPlayer->onCreatureSay(player, type, text); if (toPlayer->isInGhostMode() && !player->isAccessPlayer()) { player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name is not online."); } else { std::ostringstream ss; ss << "Message sent to " << toPlayer->getName() << '.'; player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); } return true; } //-- bool Game::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const { return map.canThrowObjectTo(fromPos, toPos, checkLineOfSight, rangex, rangey); } bool Game::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const { return map.isSightClear(fromPos, toPos, floorCheck); } bool Game::internalCreatureTurn(Creature* creature, Direction dir) { if (creature->getDirection() == dir) { return false; } creature->setDirection(dir); //send to client SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureTurn(creature); } return true; } bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, bool ghostMode, SpectatorVec* spectatorsPtr/* = nullptr*/, const Position* pos/* = nullptr*/) { if (text.empty()) { return false; } if (!pos) { pos = &creature->getPosition(); } SpectatorVec spectators; if (!spectatorsPtr || spectatorsPtr->empty()) { // This somewhat complex construct ensures that the cached SpectatorVec // is used if available and if it can be used, else a local vector is // used (hopefully the compiler will optimize away the construction of // the temporary when it's not used). if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) { map.getSpectators(spectators, *pos, false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); } else { map.getSpectators(spectators, *pos, true, false, 30, 30, 30, 30); } } else { spectators = (*spectatorsPtr); } //send to client for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { if (!ghostMode || tmpPlayer->canSeeCreature(creature)) { tmpPlayer->sendCreatureSay(creature, type, text, pos); } } } //event method for (Creature* spectator : spectators) { spectator->onCreatureSay(creature, type, text); } return true; } void Game::checkCreatureWalk(uint32_t creatureId) { Creature* creature = getCreatureByID(creatureId); if (creature && creature->getHealth() > 0) { creature->onWalk(); cleanup(); } } void Game::updateCreatureWalk(uint32_t creatureId) { Creature* creature = getCreatureByID(creatureId); if (creature && creature->getHealth() > 0) { creature->goToFollowCreature(); } } void Game::checkCreatureAttack(uint32_t creatureId) { Creature* creature = getCreatureByID(creatureId); if (creature && creature->getHealth() > 0) { creature->onAttacking(0); } } void Game::addCreatureCheck(Creature* creature) { creature->creatureCheck = true; if (creature->inCheckCreaturesVector) { // already in a vector return; } creature->inCheckCreaturesVector = true; checkCreatureLists[uniform_random(0, EVENT_CREATURECOUNT - 1)].push_back(creature); creature->incrementReferenceCounter(); } void Game::removeCreatureCheck(Creature* creature) { if (creature->inCheckCreaturesVector) { creature->creatureCheck = false; } } void Game::checkCreatures(size_t index) { g_scheduler.addEvent(createSchedulerTask(EVENT_CHECK_CREATURE_INTERVAL, std::bind(&Game::checkCreatures, this, (index + 1) % EVENT_CREATURECOUNT))); auto& checkCreatureList = checkCreatureLists[index]; auto it = checkCreatureList.begin(), end = checkCreatureList.end(); while (it != end) { Creature* creature = *it; if (creature->creatureCheck) { if (creature->getHealth() > 0) { creature->onThink(EVENT_CREATURE_THINK_INTERVAL); creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL); creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL); } else { creature->onDeath(); } ++it; } else { creature->inCheckCreaturesVector = false; it = checkCreatureList.erase(it); ReleaseCreature(creature); } } cleanup(); } void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta) { creature->setSpeed(varSpeedDelta); //send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), false, true); for (Creature* spectator : spectators) { spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed()); } } void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outfit) { if (!g_events->eventCreatureOnChangeOutfit(creature, outfit)) { return; } creature->setCurrentOutfit(outfit); if (creature->isInvisible()) { return; } //send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit); } } void Game::internalCreatureChangeVisible(Creature* creature, bool visible) { //send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureChangeVisible(creature, visible); } } void Game::changeLight(const Creature* creature) { //send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureLight(creature); } } bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field) { if (damage.type == COMBAT_NONE) { return true; } if (target->getPlayer() && target->isInGhostMode()) { return true; } if (damage.value > 0) { return false; } static const auto sendBlockEffect = [this](BlockType_t blockType, CombatType_t combatType, const Position& targetPos) { if (blockType == BLOCK_DEFENSE) { addMagicEffect(targetPos, CONST_ME_POFF); } else if (blockType == BLOCK_ARMOR) { addMagicEffect(targetPos, CONST_ME_BLOCKHIT); } else if (blockType == BLOCK_IMMUNITY) { uint8_t hitEffect = 0; switch (combatType) { case COMBAT_UNDEFINEDDAMAGE: { return; } case COMBAT_ENERGYDAMAGE: case COMBAT_FIREDAMAGE: case COMBAT_PHYSICALDAMAGE: { hitEffect = CONST_ME_BLOCKHIT; break; } case COMBAT_EARTHDAMAGE: { hitEffect = CONST_ME_GREEN_RINGS; break; } default: { hitEffect = CONST_ME_POFF; break; } } addMagicEffect(targetPos, hitEffect); } }; BlockType_t primaryBlockType; if (damage.type != COMBAT_NONE) { damage.value = -damage.value; primaryBlockType = target->blockHit(attacker, damage.type, damage.value, checkDefense, checkArmor, field); damage.value = -damage.value; sendBlockEffect(primaryBlockType, damage.type, target->getPosition()); } else { primaryBlockType = BLOCK_NONE; } return (primaryBlockType != BLOCK_NONE); } void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect) { switch (combatType) { case COMBAT_PHYSICALDAMAGE: { Item* splash = nullptr; switch (target->getRace()) { case RACE_VENOM: color = TEXTCOLOR_LIGHTGREEN; effect = CONST_ME_HITBYPOISON; splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_SLIME); break; case RACE_BLOOD: color = TEXTCOLOR_RED; effect = CONST_ME_DRAWBLOOD; splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD); break; case RACE_UNDEAD: color = TEXTCOLOR_LIGHTGREY; effect = CONST_ME_HITAREA; break; case RACE_FIRE: color = TEXTCOLOR_ORANGE; effect = CONST_ME_DRAWBLOOD; break; default: color = TEXTCOLOR_NONE; effect = CONST_ME_NONE; break; } if (splash) { internalAddItem(target->getTile(), splash, INDEX_WHEREEVER, FLAG_NOLIMIT); startDecay(splash); } break; } case COMBAT_ENERGYDAMAGE: { color = TEXTCOLOR_LIGHTBLUE; effect = CONST_ME_ENERGYHIT; break; } case COMBAT_EARTHDAMAGE: { color = TEXTCOLOR_LIGHTGREEN; effect = CONST_ME_GREEN_RINGS; break; } case COMBAT_FIREDAMAGE: { color = TEXTCOLOR_ORANGE; effect = CONST_ME_HITBYFIRE; break; } case COMBAT_LIFEDRAIN: { color = TEXTCOLOR_RED; effect = CONST_ME_MAGIC_RED; break; } case COMBAT_DROWNDAMAGE: { color = TEXTCOLOR_LIGHTBLUE; effect = CONST_ME_LOSEENERGY; break; } default: { color = TEXTCOLOR_NONE; effect = CONST_ME_NONE; break; } } } bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage) { const Position& targetPos = target->getPosition(); if (damage.value > 0) { if (target->getHealth() <= 0) { return false; } if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); if (!events.empty()) { for (CreatureEvent* creatureEvent : events) { creatureEvent->executeHealthChange(target, attacker, damage); } damage.origin = ORIGIN_NONE; return combatChangeHealth(attacker, target, damage); } } int32_t realHealthChange = target->getHealth(); target->gainHealth(attacker, damage.value); realHealthChange = target->getHealth() - realHealthChange; if (realHealthChange > 0 && !target->isInGhostMode()) { addMagicEffect(targetPos, CONST_ME_MAGIC_BLUE); } } else { if (Monster* monster = target->getMonster()) { // makes monsters aggressive when damaged // basically stands for UNDERATTACK stance under CipSoft servers // the attacker must be valid everytime (avoid field ticks damage to trigger condition) if (!monster->hasCondition(CONDITION_AGGRESSIVE) && attacker) { Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_AGGRESSIVE, 3000); monster->addCondition(condition, true); } } if (!target->isAttackable()) { if (!target->isInGhostMode()) { addMagicEffect(targetPos, CONST_ME_POFF); } return true; } Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); } else { attackerPlayer = nullptr; } damage.value = std::abs(damage.value); int32_t healthChange = damage.value; if (healthChange == 0) { return true; } Player* targetPlayer = target->getPlayer(); SpectatorVec spectators; if (target->hasCondition(CONDITION_MANASHIELD) && damage.type != COMBAT_UNDEFINEDDAMAGE) { int32_t manaDamage = std::min(targetPlayer->getMana(), healthChange); if (manaDamage != 0) { if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE); if (!events.empty()) { for (CreatureEvent* creatureEvent : events) { creatureEvent->executeManaChange(target, attacker, damage); } healthChange = damage.value; if (healthChange == 0) { return true; } manaDamage = std::min(targetPlayer->getMana(), healthChange); } } targetPlayer->drainMana(attacker, manaDamage); map.getSpectators(spectators, targetPos, true, true); addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY); std::string damageString = std::to_string(manaDamage); if (targetPlayer) { std::stringstream ss; if (!attacker) { ss << "You lose " << damageString << " mana."; } else if (targetPlayer == attackerPlayer) { ss << "You lose " << damageString << " mana due to your own attack."; } else { ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; } targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); } for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); } damage.value -= manaDamage; if (damage.value < 0) { damage.value = 0; } } } int32_t realDamage = damage.value; if (realDamage == 0) { return true; } int32_t targetHealth = target->getHealth(); if (damage.value >= targetHealth) { damage.value = targetHealth; } realDamage = damage.value; if (realDamage == 0) { return true; } if (spectators.empty()) { map.getSpectators(spectators, targetPos, true, true); } TextColor_t color = TEXTCOLOR_NONE; uint8_t hitEffect; if (damage.value) { combatGetTypeInfo(damage.type, target, color, hitEffect); if (hitEffect != CONST_ME_NONE) { addMagicEffect(spectators, targetPos, hitEffect); } } if (color != TEXTCOLOR_NONE) { std::string damageString = std::to_string(realDamage) + (realDamage != 1 ? " hitpoints" : " hitpoint"); if (targetPlayer) { std::stringstream ss; if (!attacker) { ss << "You lose " << damageString << "."; } else if (targetPlayer == attackerPlayer) { ss << "You lose " << damageString << " due to your own attack."; } else { ss << "You lose " << damageString << " due to an attack by " << attacker->getNameDescription() << '.'; } targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); } std::string realDamageStr = std::to_string(realDamage); for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); tmpPlayer->sendAnimatedText(targetPos, color, realDamageStr); } } if (realDamage >= targetHealth) { for (CreatureEvent* creatureEvent : target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) { if (!creatureEvent->executeOnPrepareDeath(target, attacker)) { return false; } } } target->drainHealth(attacker, realDamage); addCreatureHealth(spectators, target); } return true; } bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& damage) { Player* targetPlayer = target->getPlayer(); if (!targetPlayer) { return true; } int32_t manaChange = damage.value; if (manaChange > 0) { if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE); if (!events.empty()) { for (CreatureEvent* creatureEvent : events) { creatureEvent->executeManaChange(target, attacker, damage); } damage.origin = ORIGIN_NONE; return combatChangeMana(attacker, target, damage); } } targetPlayer->changeMana(manaChange); } else { const Position& targetPos = target->getPosition(); if (!target->isAttackable()) { if (!target->isInGhostMode()) { addMagicEffect(targetPos, CONST_ME_POFF); } return false; } Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); } else { attackerPlayer = nullptr; } int32_t manaLoss = std::min(targetPlayer->getMana(), -manaChange); BlockType_t blockType = target->blockHit(attacker, COMBAT_MANADRAIN, manaLoss); if (blockType != BLOCK_NONE) { addMagicEffect(targetPos, CONST_ME_POFF); return false; } if (manaLoss <= 0) { return true; } if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE); if (!events.empty()) { for (CreatureEvent* creatureEvent : events) { creatureEvent->executeManaChange(target, attacker, damage); } damage.origin = ORIGIN_NONE; return combatChangeMana(attacker, target, damage); } } targetPlayer->drainMana(attacker, manaLoss); std::string damageString = std::to_string(manaLoss); SpectatorVec spectators; map.getSpectators(spectators, targetPos, false, true); if (targetPlayer) { std::stringstream ss; if (!attacker) { ss << "You lose " << damageString << " mana."; } else if (targetPlayer == attackerPlayer) { ss << "You lose " << damageString << " mana due to your own attack."; } else { ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; } targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); } for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); } } return true; } void Game::addCreatureHealth(const Creature* target) { SpectatorVec spectators; map.getSpectators(spectators, target->getPosition(), true, true); addCreatureHealth(spectators, target); } void Game::addCreatureHealth(const SpectatorVec& spectators, const Creature* target) { for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureHealth(target); } } } void Game::addMagicEffect(const Position& pos, uint8_t effect) { SpectatorVec spectators; map.getSpectators(spectators, pos, true, true); addMagicEffect(spectators, pos, effect); } void Game::addMagicEffect(const SpectatorVec& spectators, const Position& pos, uint8_t effect) { for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendMagicEffect(pos, effect); } } } void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) { SpectatorVec spectators; map.getSpectators(spectators, fromPos, false, true); map.getSpectators(spectators, toPos, false, true); addDistanceEffect(spectators, fromPos, toPos, effect); } void Game::addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, uint8_t effect) { for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendDistanceShoot(fromPos, toPos, effect); } } } void Game::addAnimatedText(const Position& pos, uint8_t color, const std::string& text) { SpectatorVec spectators; map.getSpectators(spectators, pos, false, true); addAnimatedText(spectators, pos, color, text); } void Game::addAnimatedText(const SpectatorVec& spectators, const Position& pos, uint8_t color, const std::string& text) { for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendAnimatedText(pos, color, text); } } } void Game::addMonsterSayText(const Position& pos, const std::string& text) { SpectatorVec spectators; map.getSpectators(spectators, pos, false, true); for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureSay(tmpPlayer, TALKTYPE_MONSTER_SAY, text, &pos); } } } void Game::startDecay(Item* item) { if (!item || !item->canDecay()) { return; } ItemDecayState_t decayState = item->getDecaying(); if (decayState == DECAYING_TRUE) { return; } if (item->getDuration() > 0) { item->incrementReferenceCounter(); item->setDecaying(DECAYING_TRUE); toDecayItems.push_front(item); } else { internalDecayItem(item); } } void Game::internalDecayItem(Item* item) { const ItemType& it = Item::items[item->getID()]; if (it.decayTo != 0) { Item* newItem = transformItem(item, it.decayTo); startDecay(newItem); } else { ReturnValue ret = internalRemoveItem(item); if (ret != RETURNVALUE_NOERROR) { std::cout << "[Debug - Game::internalDecayItem] internalDecayItem failed, error code: " << static_cast(ret) << ", item id: " << item->getID() << std::endl; } } } void Game::checkDecay() { g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this))); size_t bucket = (lastBucket + 1) % EVENT_DECAY_BUCKETS; auto it = decayItems[bucket].begin(), end = decayItems[bucket].end(); while (it != end) { Item* item = *it; if (!item->canDecay()) { item->setDecaying(DECAYING_FALSE); ReleaseItem(item); it = decayItems[bucket].erase(it); continue; } int32_t duration = item->getDuration(); int32_t decreaseTime = std::min(EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS, duration); duration -= decreaseTime; item->decreaseDuration(decreaseTime); if (duration <= 0) { it = decayItems[bucket].erase(it); internalDecayItem(item); ReleaseItem(item); } else if (duration < EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) { it = decayItems[bucket].erase(it); size_t newBucket = (bucket + ((duration + EVENT_DECAYINTERVAL / 2) / 1000)) % EVENT_DECAY_BUCKETS; if (newBucket == bucket) { internalDecayItem(item); ReleaseItem(item); } else { decayItems[newBucket].push_back(item); } } else { ++it; } } lastBucket = bucket; cleanup(); } void Game::checkLight() { g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, std::bind(&Game::checkLight, this))); lightHour += lightHourDelta; if (lightHour > 1440) { lightHour -= 1440; } if (std::abs(lightHour - SUNRISE) < 2 * lightHourDelta) { lightState = LIGHT_STATE_SUNRISE; } else if (std::abs(lightHour - SUNSET) < 2 * lightHourDelta) { lightState = LIGHT_STATE_SUNSET; } int32_t newLightLevel = lightLevel; bool lightChange = false; switch (lightState) { case LIGHT_STATE_SUNRISE: { newLightLevel += (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30; lightChange = true; break; } case LIGHT_STATE_SUNSET: { newLightLevel -= (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30; lightChange = true; break; } default: break; } if (newLightLevel <= LIGHT_LEVEL_NIGHT) { lightLevel = LIGHT_LEVEL_NIGHT; lightState = LIGHT_STATE_NIGHT; } else if (newLightLevel >= LIGHT_LEVEL_DAY) { lightLevel = LIGHT_LEVEL_DAY; lightState = LIGHT_STATE_DAY; } else { lightLevel = newLightLevel; } if (lightChange) { LightInfo lightInfo = getWorldLightInfo(); for (const auto& it : players) { it.second->sendWorldLight(lightInfo); } } } LightInfo Game::getWorldLightInfo() const { return { lightLevel, 0xD7 }; } void Game::shutdown() { saveGameState(); std::cout << "Shutting down..." << std::flush; g_scheduler.shutdown(); g_databaseTasks.shutdown(); g_dispatcher.shutdown(); map.spawns.clear(); raids.clear(); cleanup(); if (serviceManager) { serviceManager->stop(); } ConnectionManager::getInstance().closeAll(); std::cout << " done!" << std::endl; } void Game::cleanup() { //free memory for (auto creature : ToReleaseCreatures) { creature->decrementReferenceCounter(); } ToReleaseCreatures.clear(); for (auto item : ToReleaseItems) { item->decrementReferenceCounter(); } ToReleaseItems.clear(); for (Item* item : toDecayItems) { const uint32_t dur = item->getDuration(); if (dur >= EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) { decayItems[lastBucket].push_back(item); } else { decayItems[(lastBucket + 1 + dur / 1000) % EVENT_DECAY_BUCKETS].push_back(item); } } toDecayItems.clear(); } void Game::ReleaseCreature(Creature* creature) { ToReleaseCreatures.push_back(creature); } void Game::ReleaseItem(Item* item) { ToReleaseItems.push_back(item); } void Game::broadcastMessage(const std::string& text, MessageClasses type) const { std::cout << "> Broadcasted message: \"" << text << "\"." << std::endl; for (const auto& it : players) { it.second->sendTextMessage(type, text); } } void Game::updateCreatureSkull(const Creature* creature) { if (getWorldType() != WORLD_TYPE_PVP) { return; } SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureSkull(creature); } } void Game::updatePlayerShield(Player* player) { SpectatorVec spectators; map.getSpectators(spectators, player->getPosition(), true, true); for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureShield(player); } } void Game::updatePremium(Account& account) { bool save = false; time_t timeNow = time(nullptr); if (account.premiumDays != 0 && account.premiumDays != std::numeric_limits::max()) { if (account.lastDay == 0) { account.lastDay = timeNow; save = true; } else { uint32_t days = (timeNow - account.lastDay) / 86400; if (days > 0) { if (days >= account.premiumDays) { account.premiumDays = 0; account.lastDay = 0; } else { account.premiumDays -= days; time_t remainder = (timeNow - account.lastDay) % 86400; account.lastDay = timeNow - remainder; } save = true; } } } else if (account.lastDay != 0) { account.lastDay = 0; save = true; } if (save && !IOLoginData::saveAccount(account)) { std::cout << "> ERROR: Failed to save account: " << account.id << "!" << std::endl; } } void Game::loadMotdNum() { Database* db = Database::getInstance(); DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'"); if (result) { motdNum = result->getNumber("value"); } else { db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')"); } result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'"); if (result) { motdHash = result->getString("value"); if (motdHash != transformToSHA1(g_config.getString(ConfigManager::MOTD))) { ++motdNum; } } else { db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')"); } } void Game::saveMotdNum() const { Database* db = Database::getInstance(); std::ostringstream query; query << "UPDATE `server_config` SET `value` = '" << motdNum << "' WHERE `config` = 'motd_num'"; db->executeQuery(query.str()); query.str(std::string()); query << "UPDATE `server_config` SET `value` = '" << transformToSHA1(g_config.getString(ConfigManager::MOTD)) << "' WHERE `config` = 'motd_hash'"; db->executeQuery(query.str()); } void Game::checkPlayersRecord() { const size_t playersOnline = getPlayersOnline(); if (playersOnline > playersRecord) { uint32_t previousRecord = playersRecord; playersRecord = playersOnline; for (const auto& it : g_globalEvents->getEventMap(GLOBALEVENT_RECORD)) { it.second->executeRecord(playersRecord, previousRecord); } updatePlayersRecord(); } } void Game::updatePlayersRecord() const { Database* db = Database::getInstance(); std::ostringstream query; query << "UPDATE `server_config` SET `value` = '" << playersRecord << "' WHERE `config` = 'players_record'"; db->executeQuery(query.str()); } void Game::loadPlayersRecord() { Database* db = Database::getInstance(); DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'players_record'"); if (result) { playersRecord = result->getNumber("value"); } else { db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('players_record', '0')"); } } uint64_t Game::getExperienceStage(uint32_t level) { if (!stagesEnabled) { return g_config.getNumber(ConfigManager::RATE_EXPERIENCE); } if (useLastStageLevel && level >= lastStageLevel) { return stages[lastStageLevel]; } return stages[level]; } bool Game::loadExperienceStages() { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file("data/XML/stages.xml"); if (!result) { printXMLError("Error - Game::loadExperienceStages", "data/XML/stages.xml", result); return false; } for (auto stageNode : doc.child("stages").children()) { if (strcasecmp(stageNode.name(), "config") == 0) { stagesEnabled = stageNode.attribute("enabled").as_bool(); } else { uint32_t minLevel, maxLevel, multiplier; pugi::xml_attribute minLevelAttribute = stageNode.attribute("minlevel"); if (minLevelAttribute) { minLevel = pugi::cast(minLevelAttribute.value()); } else { minLevel = 1; } pugi::xml_attribute maxLevelAttribute = stageNode.attribute("maxlevel"); if (maxLevelAttribute) { maxLevel = pugi::cast(maxLevelAttribute.value()); } else { maxLevel = 0; lastStageLevel = minLevel; useLastStageLevel = true; } pugi::xml_attribute multiplierAttribute = stageNode.attribute("multiplier"); if (multiplierAttribute) { multiplier = pugi::cast(multiplierAttribute.value()); } else { multiplier = 1; } if (useLastStageLevel) { stages[lastStageLevel] = multiplier; } else { for (uint32_t i = minLevel; i <= maxLevel; ++i) { stages[i] = multiplier; } } } } return true; } void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) { if (playerId == invitedId) { return; } Player* player = getPlayerByID(playerId); if (!player) { return; } Player* invitedPlayer = getPlayerByID(invitedId); if (!invitedPlayer || invitedPlayer->isInviting(player)) { return; } if (invitedPlayer->getParty()) { std::ostringstream ss; ss << invitedPlayer->getName() << " is already in a party."; player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); return; } Party* party = player->getParty(); if (!party) { party = new Party(player); } else if (party->getLeader() != player) { return; } party->invitePlayer(*invitedPlayer); } void Game::playerJoinParty(uint32_t playerId, uint32_t leaderId) { Player* player = getPlayerByID(playerId); if (!player) { return; } Player* leader = getPlayerByID(leaderId); if (!leader || !leader->isInviting(player)) { return; } Party* party = leader->getParty(); if (!party || party->getLeader() != leader) { return; } if (player->getParty()) { player->sendTextMessage(MESSAGE_INFO_DESCR, "You are already in a party."); return; } party->joinParty(*player); } void Game::playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId) { Player* player = getPlayerByID(playerId); if (!player) { return; } Party* party = player->getParty(); if (!party || party->getLeader() != player) { return; } Player* invitedPlayer = getPlayerByID(invitedId); if (!invitedPlayer || !player->isInviting(invitedPlayer)) { return; } party->revokeInvitation(*invitedPlayer); } void Game::playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId) { Player* player = getPlayerByID(playerId); if (!player) { return; } Party* party = player->getParty(); if (!party || party->getLeader() != player) { return; } Player* newLeader = getPlayerByID(newLeaderId); if (!newLeader || !player->isPartner(newLeader)) { return; } party->passPartyLeadership(newLeader); } void Game::playerLeaveParty(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } Party* party = player->getParty(); if (!party || player->hasCondition(CONDITION_INFIGHT)) { return; } party->leaveParty(player); } void Game::playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive) { Player* player = getPlayerByID(playerId); if (!player) { return; } Party* party = player->getParty(); if (!party || player->hasCondition(CONDITION_INFIGHT)) { return; } party->setSharedExperience(player, sharedExpActive); } void Game::playerProcessRuleViolationReport(uint32_t playerId, const std::string& name) { Player* player = getPlayerByID(playerId); if (!player) { return; } if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { return; } Player* reporter = getPlayerByName(name); if (!reporter) { return; } auto it = ruleViolations.find(reporter->getID()); if (it == ruleViolations.end()) { return; } RuleViolation& ruleViolation = it->second; if (!ruleViolation.pending) { return; } ruleViolation.gamemasterId = player->getID(); ruleViolation.pending = false; ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); if (channel) { for (auto userPtr : channel->getUsers()) { if (userPtr.second) { userPtr.second->sendRemoveRuleViolationReport(reporter->getName()); } } } } void Game::playerCloseRuleViolationReport(uint32_t playerId, const std::string& name) { Player* player = getPlayerByID(playerId); if (!player) { return; } Player* reporter = getPlayerByName(name); if (!reporter) { return; } closeRuleViolationReport(reporter); } void Game::playerCancelRuleViolationReport(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } cancelRuleViolationReport(player); } void Game::playerReportRuleViolationReport(Player* player, const std::string& text) { auto it = ruleViolations.find(player->getID()); if (it != ruleViolations.end()) { player->sendCancelMessage("You already have a pending rule violation report. Close it before starting a new one."); return; } RuleViolation ruleViolation = RuleViolation(player->getID(), text); ruleViolations[player->getID()] = ruleViolation; ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); if (channel) { for (auto userPtr : channel->getUsers()) { if (userPtr.second) { userPtr.second->sendToChannel(player, TALKTYPE_RVR_CHANNEL, text, CHANNEL_RULE_REP); } } } } void Game::playerContinueRuleViolationReport(Player* player, const std::string& text) { auto it = ruleViolations.find(player->getID()); if (it == ruleViolations.end()) { return; } RuleViolation& rvr = it->second; Player* toPlayer = getPlayerByID(rvr.gamemasterId); if (!toPlayer) { return; } toPlayer->sendCreatureSay(player, TALKTYPE_RVR_CONTINUE, text, 0); player->sendTextMessage(MESSAGE_STATUS_SMALL, "Message sent to Counsellor."); } void Game::kickPlayer(uint32_t playerId, bool displayEffect) { Player* player = getPlayerByID(playerId); if (!player) { return; } player->kickPlayer(displayEffect); } void Game::playerReportBug(uint32_t playerId, const std::string& message) { Player* player = getPlayerByID(playerId); if (!player) { return; } const Position& position = player->getPosition(); g_events->eventPlayerOnReportBug(player, message, position); } void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment) { Player* player = getPlayerByID(playerId); if (!player) { return; } // TODO: move debug assertions to database FILE* file = fopen("client_assertions.txt", "a"); if (file) { fprintf(file, "----- %s - %s (%s) -----\n", formatDate(time(nullptr)).c_str(), player->getName().c_str(), convertIPToString(player->getIP()).c_str()); fprintf(file, "%s\n%s\n%s\n%s\n", assertLine.c_str(), date.c_str(), description.c_str(), comment.c_str()); fclose(file); } } void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer) { Player* player = getPlayerByID(playerId); if (!player) { return; } for (CreatureEvent* creatureEvent : player->getCreatureEvents(CREATURE_EVENT_EXTENDED_OPCODE)) { creatureEvent->executeExtendedOpcode(player, opcode, buffer); } } void Game::closeRuleViolationReport(Player* player) { const auto it = ruleViolations.find(player->getID()); if (it == ruleViolations.end()) { return; } ruleViolations.erase(it); player->sendLockRuleViolationReport(); ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); if (channel) { for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { if (ut->second) { ut->second->sendRemoveRuleViolationReport(player->getName()); } } } } void Game::cancelRuleViolationReport(Player* player) { const auto it = ruleViolations.find(player->getID()); if (it == ruleViolations.end()) { return; } RuleViolation& ruleViolation = it->second; Player* gamemaster = getPlayerByID(ruleViolation.gamemasterId); if (!ruleViolation.pending && gamemaster) { // Send to the responder gamemaster->sendRuleViolationCancel(player->getName()); } // Send to channel ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); if (channel) { for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { if (ut->second) { ut->second->sendRemoveRuleViolationReport(player->getName()); } } } // Erase it ruleViolations.erase(it); } void Game::forceAddCondition(uint32_t creatureId, Condition* condition) { Creature* creature = getCreatureByID(creatureId); if (!creature) { delete condition; return; } creature->addCondition(condition, true); } void Game::forceRemoveCondition(uint32_t creatureId, ConditionType_t type) { Creature* creature = getCreatureByID(creatureId); if (!creature) { return; } creature->removeCondition(type, true); } void Game::addPlayer(Player* player) { const std::string& lowercase_name = asLowerCaseString(player->getName()); mappedPlayerNames[lowercase_name] = player; wildcardTree.insert(lowercase_name); players[player->getID()] = player; } void Game::removePlayer(Player* player) { const std::string& lowercase_name = asLowerCaseString(player->getName()); mappedPlayerNames.erase(lowercase_name); wildcardTree.remove(lowercase_name); players.erase(player->getID()); } void Game::addNpc(Npc* npc) { npcs[npc->getID()] = npc; } void Game::removeNpc(Npc* npc) { npcs.erase(npc->getID()); } void Game::addMonster(Monster* monster) { monsters[monster->getID()] = monster; } void Game::removeMonster(Monster* monster) { monsters.erase(monster->getID()); } Guild* Game::getGuild(uint32_t id) const { auto it = guilds.find(id); if (it == guilds.end()) { return nullptr; } return it->second; } void Game::addGuild(Guild* guild) { guilds[guild->getId()] = guild; } void Game::removeGuild(uint32_t guildId) { guilds.erase(guildId); } void Game::internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable) { if (stackable) { for (Item* item : itemList) { if (item->getItemCount() > amount) { internalRemoveItem(item, amount); break; } else { amount -= item->getItemCount(); internalRemoveItem(item); } } } else { for (Item* item : itemList) { internalRemoveItem(item); } } } BedItem* Game::getBedBySleeper(uint32_t guid) const { auto it = bedSleepersMap.find(guid); if (it == bedSleepersMap.end()) { return nullptr; } return it->second; } void Game::setBedSleeper(BedItem* bed, uint32_t guid) { bedSleepersMap[guid] = bed; } void Game::removeBedSleeper(uint32_t guid) { auto it = bedSleepersMap.find(guid); if (it != bedSleepersMap.end()) { bedSleepersMap.erase(it); } } bool Game::reload(ReloadTypes_t reloadType) { switch (reloadType) { case RELOAD_TYPE_ACTIONS: return g_actions->reload(); case RELOAD_TYPE_CHAT: return g_chat->load(); case RELOAD_TYPE_CONFIG: return g_config.reload(); case RELOAD_TYPE_CREATURESCRIPTS: return g_creatureEvents->reload(); case RELOAD_TYPE_EVENTS: return g_events->load(); case RELOAD_TYPE_GLOBALEVENTS: return g_globalEvents->reload(); case RELOAD_TYPE_ITEMS: return Item::items.reload(); case RELOAD_TYPE_MONSTERS: return g_monsters.reload(); case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload(); case RELOAD_TYPE_NPCS: { Npcs::reload(); return true; } case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup(); case RELOAD_TYPE_SPELLS: { if (!g_spells->reload()) { std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; std::terminate(); } else if (!g_monsters.reload()) { std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; std::terminate(); } return true; } case RELOAD_TYPE_TALKACTIONS: return g_talkActions->reload(); default: { if (!g_spells->reload()) { std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; std::terminate(); return false; } else if (!g_monsters.reload()) { std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; std::terminate(); return false; } g_actions->reload(); g_config.reload(); g_creatureEvents->reload(); g_monsters.reload(); g_moveEvents->reload(); Npcs::reload(); raids.reload() && raids.startup(); g_talkActions->reload(); Item::items.reload(); g_globalEvents->reload(); g_events->load(); g_chat->load(); return true; } } }