/** * The Forgotten Server - a free and open-source MMORPG server emulator * Copyright (C) 2019 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 "actions.h" #include "bed.h" #include "configmanager.h" #include "creature.h" #include "creatureevent.h" #include "databasetasks.h" #include "events.h" #include "game.h" #include "globalevent.h" #include "iologindata.h" #include "iomarket.h" #include "items.h" #include "monster.h" #include "movement.h" #include "scheduler.h" #include "script.h" #include "server.h" #include "spells.h" #include "talkaction.h" #include "weapons.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 CreatureEvents *g_creatureEvents; extern Events *g_events; extern Monsters g_monsters; extern MoveEvents *g_moveEvents; extern Weapons *g_weapons; extern Scripts *g_scripts; Game::Game() { offlineTrainingWindow.choices.emplace_back("Rifle Combat and Shielding", SKILL_SWORD); offlineTrainingWindow.choices.emplace_back("Pistol Combat and Shielding", SKILL_AXE); offlineTrainingWindow.choices.emplace_back("Machine-Gun Combat and Shielding", SKILL_CLUB); offlineTrainingWindow.choices.emplace_back("Shotgun Combat and Shielding", SKILL_DISTANCE); offlineTrainingWindow.choices.emplace_back("Magic Level and Shielding", SKILL_MAGLEVEL); offlineTrainingWindow.buttons.emplace_back("Okay", 1); offlineTrainingWindow.buttons.emplace_back("Cancel", 0); offlineTrainingWindow.defaultEnterButton = 1; offlineTrainingWindow.defaultEscapeButton = 0; offlineTrainingWindow.priority = true; } 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(); quests.loadFromXml(); mounts.loadFromXml(); auras.loadFromXml(); wings.loadFromXml(); shaders.loadFromXml(); runeEffects.loadFromXml(); 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(); g_databaseTasks.flush(); 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; } if (parentContainer->getID() == ITEM_BROWSEFIELD) { Tile *tile = parentContainer->getTile(); if (tile && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { if (tile->hasProperty(CONST_PROP_ISVERTICAL)) { if (player->getPosition().x + 1 == tile->getPosition().x) { return nullptr; } } else { // horizontal if (player->getPosition().y + 1 == tile->getPosition().y) { 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.getItemIdByClientId(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; } auto it = mappedPlayerGuids.find(guid); if (it == mappedPlayerGuids.end()) { return nullptr; } return it->second; } 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 (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos, player->getPosition())) { // need to walk to the creature first before moving it std::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 ¤tPos = creature->getPosition(); Position destPos = getNextPosition(direction, currentPos); Player *player = creature->getPlayer(); bool diagonalMovement = (direction & DIRECTION_DIAGONAL_MASK) != 0; if (player && !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)) { flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; if (!tmpTile->hasFlag(TILESTATE_FLOORCHANGE)) { player->setDirection(direction); destPos.z--; } } } } // try go down if (currentPos.z != 7 && currentPos.z == destPos.z) { Tile *tmpTile = map.getTile(destPos.x, destPos.y, destPos.z); if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { tmpTile = map.getTile(destPos.x, destPos.y, destPos.z + 1); if (tmpTile && tmpTile->hasHeight(3)) { flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; player->setDirection(direction); destPos.z++; } } } } Tile *toTile = map.getTile(destPos); 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->getClientID() != 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() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { 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::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, 0, player, nullptr, &fromPos, &toPos); 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::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; } 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, count, nullptr, 0, player, nullptr, &fromPos, &toPos); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); } } 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*/, const Position *fromPos /*= nullptr*/, const Position *toPos /*= nullptr*/) { Player *actorPlayer = actor ? actor->getPlayer() : nullptr; if (actorPlayer && fromPos && toPos) { if (!g_events->eventPlayerOnMoveItem(actorPlayer, item, count, *fromPos, *toPos, fromCylinder, toCylinder)) { return RETURNVALUE_NOTPOSSIBLE; } } Tile *fromTile = fromCylinder->getTile(); if (fromTile) { auto it = browseFields.find(fromTile); if (it != browseFields.end() && it->second == fromCylinder) { fromCylinder = fromTile; } } 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()) { 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->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; } if (moveItem && moveItem->getDuration() > 0) { if (moveItem->getDecaying() != DECAYING_TRUE) { moveItem->incrementReferenceCounter(); moveItem->setDecaying(DECAYING_TRUE); toDecayItems.push_front(moveItem); } } if (actorPlayer && fromPos && toPos) { g_events->eventPlayerOnItemMoved(actorPlayer, item, count, *fromPos, *toPos, fromCylinder, toCylinder); } 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->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); } } if (item->getDuration() > 0) { item->incrementReferenceCounter(); item->setDecaying(DECAYING_TRUE); toDecayItems.push_front(item); } 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; } Tile *fromTile = cylinder->getTile(); if (fromTile) { auto it = browseFields.find(fromTile); if (it != browseFields.end() && it->second == cylinder) { cylinder = fromTile; } } 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()) { item->onRemoved(); if (item->canDecay()) { decayItems->remove(item); } ReleaseItem(item); } cylinder->postRemoveNotification(item, nullptr, index); } 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; } Tile *fromTile = cylinder->getTile(); if (fromTile) { auto it = browseFields.find(fromTile); if (it != browseFields.end() && it->second == cylinder) { cylinder = fromTile; } } 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 = item->getDecayTo(); } 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); if (newItem->getDuration() > 0) { if (newItem->getDecaying() != DECAYING_TRUE) { newItem->incrementReferenceCounter(); newItem->setDecaying(DECAYING_TRUE); toDecayItems.push_front(newItem); } } 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; } 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; } Item *searchForItem(Container *container, uint16_t itemId) { for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { if ((*it)->getID() == itemId) { return *it; } } return nullptr; } slots_t getSlotType(const ItemType &it) { slots_t slot = CONST_SLOT_RIGHT; if (it.weaponType != WeaponType_t::WEAPON_SHIELD) { int32_t slotPosition = it.slotPosition; if (slotPosition & SLOTP_HEAD) { slot = CONST_SLOT_HEAD; } else if (slotPosition & SLOTP_NECKLACE) { slot = CONST_SLOT_NECKLACE; } else if (slotPosition & SLOTP_ARMOR) { slot = CONST_SLOT_ARMOR; } else if (slotPosition & SLOTP_LEGS) { slot = CONST_SLOT_LEGS; } else if (slotPosition & SLOTP_FEET) { slot = CONST_SLOT_FEET; } else if (slotPosition & SLOTP_RING) { slot = CONST_SLOT_RING; } else if (slotPosition & SLOTP_AMMO) { slot = CONST_SLOT_AMMO; } else if (slotPosition & SLOTP_TWO_HAND || slotPosition & SLOTP_LEFT) { slot = CONST_SLOT_LEFT; } } return slot; } // Implementation of player invoked events void Game::playerEquipItem(uint32_t playerId, uint16_t spriteId) { Player *player = getPlayerByID(playerId); if (!player) { return; } Item *item = player->getInventoryItem(CONST_SLOT_BACKPACK); if (!item) { return; } Container *backpack = item->getContainer(); if (!backpack) { return; } const ItemType &it = Item::items.getItemIdByClientId(spriteId); slots_t slot = getSlotType(it); Item *slotItem = player->getInventoryItem(slot); Item *equipItem = searchForItem(backpack, it.id); if (slotItem && slotItem->getID() == it.id && (!it.stackable || slotItem->getItemCount() == 100 || !equipItem)) { internalMoveItem(slotItem->getParent(), player, CONST_SLOT_WHEREEVER, slotItem, slotItem->getItemCount(), nullptr); } else if (equipItem) { internalMoveItem(equipItem->getParent(), player, slot, equipItem, equipItem->getItemCount(), nullptr); } } void Game::playerMove(uint32_t playerId, Direction direction) { Player *player = getPlayerByID(playerId); if (!player) { return; } player->resetIdleTime(); player->setNextWalkActionTask(nullptr); player->startAutoWalk(std::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; } const InvitedMap *invitedUsers = channel->getInvitedUsers(); const UsersMap *users; if (!channel->isPublicChannel()) { users = &channel->getUsers(); } else { users = nullptr; } player->sendChannel(channel->getId(), channel->getName(), users, invitedUsers); } 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::playerCloseNpcChannel(uint32_t playerId) { Player *player = getPlayerByID(playerId); if (!player) { return; } SpectatorVec spectators; map.getSpectators(spectators, player->getPosition()); for (Creature *spectator : spectators) { if (Npc *npc = spectator->getNpc()) { npc->onPlayerCloseChannel(player); } } } 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::playerReceiveNewPing(uint32_t playerId, uint16_t ping, uint16_t fps) { Player *player = getPlayerByID(playerId); if (!player) { return; } player->receivePing(); player->setLocalPing(ping); player->setFPS(fps); } void Game::playerAutoWalk(uint32_t playerId, const std::list &listDir) { Player *player = getPlayerByID(playerId); if (!player) { return; } player->resetIdleTime(); player->setNextWalkTask(nullptr); player->startAutoWalk(listDir); } void Game::playerNewWalk(uint32_t playerId, Position pos, uint8_t flags, std::list listDir) { Player *player = getPlayerByID(playerId); if (!player) { return; } player->resetIdleTime(); player->setNextWalkTask(nullptr); player->setNextWalkActionTask(nullptr); // bool withPreWalk = flags & 0x01; bool autoWalk = flags & 0x04; if (pos.x != 0 && pos.y != 0 && pos != player->getPosition()) { auto &dirs = player->getListWalkDir(); Position nextpos = player->getPosition(); int limit = 3; for (auto &dir : dirs) { nextpos = getNextPosition(dir, nextpos); if (--limit == 0) break; } if (!autoWalk) { // manual walk desync, check if can be fixed if (limit == 0 || nextpos != pos) { player->sendNewCancelWalk(); return; } for (auto &dir : listDir) { dirs.push_back(dir); } return; } else { // auto walk desync, check if can be fixed if (limit > 0 && nextpos == pos) { for (auto &dir : listDir) { dirs.push_back(dir); } return; } // can't be fixed, so maybe find another way // WARNING: This loop may use extra cpu but makes autowalk (map click) // much better for (int x = 0; x < 3; ++x) { if (listDir.empty()) { player->sendNewCancelWalk(); return; } for (int i = 0; i < 2; ++i) { if (listDir.empty()) break; pos = getNextPosition(listDir.front(), pos); listDir.pop_front(); } std::list newPath; if (player->getPathTo(pos, newPath, 0, 0, false, true)) { newPath.reverse(); for (auto &it : newPath) listDir.push_front(it); break; } } } } 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->isUseable() || item->getClientID() != 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, 0, player, nullptr, &fromPos, &toPos); 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::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->isUseable() || item->getClientID() != spriteId) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } ReturnValue ret = g_actions->canUse(player, pos); if (ret != RETURNVALUE_NOERROR) { if (ret == RETURNVALUE_TOOFARAWAY) { std::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->isUseable() || item->getClientID() != 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, 0, player, nullptr, &fromPos, &toPos); 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::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) { Tile *tile = container->getTile(); if (!tile) { return; } auto it = browseFields.find(tile); if (it == browseFields.end()) { parentContainer = new Container(tile); parentContainer->incrementReferenceCounter(); browseFields[tile] = parentContainer; g_scheduler.addEvent( createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); } else { parentContainer = it->second; } } 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->getClientID() != spriteId || !item->isRotatable() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::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; } for (auto creatureEvent : player->getCreatureEvents(CREATURE_EVENT_TEXTEDIT)) { if (!creatureEvent->executeTextEdit(player, writeItem, text)) { player->setWriteItem(nullptr); 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::playerBrowseField(uint32_t playerId, const Position &pos) { Player *player = getPlayerByID(playerId); if (!player) { return; } const Position &playerPos = player->getPosition(); if (playerPos.z != pos.z) { player->sendCancelMessage(playerPos.z > pos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); return; } if (!Position::areInRange<1, 1>(playerPos, pos)) { std::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::playerBrowseField, this, playerId, pos)); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } return; } Tile *tile = map.getTile(pos); if (!tile) { return; } if (!g_events->eventPlayerOnBrowseField(player, pos)) { return; } Container *container; auto it = browseFields.find(tile); if (it == browseFields.end()) { container = new Container(tile); container->incrementReferenceCounter(); browseFields[tile] = container; g_scheduler.addEvent( createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); } else { container = it->second; } uint8_t dummyContainerId = 0xF - ((pos.x % 3) * 3 + (pos.y % 3)); Container *openContainer = player->getContainerByID(dummyContainerId); if (openContainer) { player->onCloseContainer(openContainer); player->closeContainer(dummyContainerId); } else { player->addContainer(dummyContainerId, container); player->sendContainer(dummyContainerId, container, false, 0); } } 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 || !container->hasPagination()) { 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::playerWrapItem(uint32_t playerId, const Position &position, uint8_t stackPos, const uint16_t spriteId) { Player *player = getPlayerByID(playerId); if (!player) { return; } Thing *thing = internalGetThing(player, position, stackPos, 0, STACKPOS_TOPDOWN_ITEM); if (!thing) { return; } Item *item = thing->getItem(); if (!item || item->getClientID() != spriteId || !item->hasAttribute(ITEM_ATTRIBUTE_WRAPID) || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } if (position.x != 0xFFFF && !Position::areInRange<1, 1, 0>(position, player->getPosition())) { std::list listDir; if (player->getPathTo(position, listDir, 0, 1, true, true)) { g_dispatcher.addTask(createTask( std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask *task = createSchedulerTask( 400, std::bind(&Game::playerWrapItem, this, playerId, position, stackPos, spriteId)); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } return; } g_events->eventPlayerOnWrapItem(player, item); } 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->getClientID() != spriteId || !tradeItem->isPickupable() || tradeItem->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { 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::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 = RETURNVALUE_NOERROR; ReturnValue playerRet = RETURNVALUE_NOERROR; // if player is trying to trade its own backpack if (tradePartner->getInventoryItem(CONST_SLOT_BACKPACK) == partnerTradeItem) { tradePartnerRet = (tradePartner->getInventoryItem( getSlotType(Item::items[playerTradeItem->getID()])) ? RETURNVALUE_NOTENOUGHROOM : RETURNVALUE_NOERROR); } if (player->getInventoryItem(CONST_SLOT_BACKPACK) == playerTradeItem) { playerRet = (player->getInventoryItem( getSlotType(Item::items[partnerTradeItem->getID()])) ? RETURNVALUE_NOTENOUGHROOM : RETURNVALUE_NOERROR); } if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { tradePartnerRet = internalAddItem(tradePartner, playerTradeItem, INDEX_WHEREEVER, 0, true); playerRet = internalAddItem(player, partnerTradeItem, INDEX_WHEREEVER, 0, true); if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { playerRet = internalRemoveItem(playerTradeItem, playerTradeItem->getItemCount(), true); tradePartnerRet = internalRemoveItem( partnerTradeItem, partnerTradeItem->getItemCount(), true); if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { tradePartnerRet = internalMoveItem( playerTradeItem->getParent(), tradePartner, INDEX_WHEREEVER, playerTradeItem, playerTradeItem->getItemCount(), nullptr, FLAG_IGNOREAUTOSTACK, nullptr, partnerTradeItem); if (tradePartnerRet == RETURNVALUE_NOERROR) { internalMoveItem(partnerTradeItem->getParent(), player, INDEX_WHEREEVER, partnerTradeItem, partnerTradeItem->getItemCount(), 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); } } g_events->eventPlayerOnTradeCompleted(player, tradePartner, playerTradeItem, partnerTradeItem, isSuccess); 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 << "\n " << 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::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreCap /* = false*/, bool inBackpacks /* = false*/) { if (amount == 0 || amount > 100) { return; } Player *player = getPlayerByID(playerId); if (!player) { return; } int32_t onBuy, onSell; Npc *merchant = player->getShopOwner(onBuy, onSell); if (!merchant) { return; } const ItemType &it = Item::items.getItemIdByClientId(spriteId); if (it.id == 0) { return; } uint8_t subType; if (it.isSplash() || it.isFluidContainer()) { subType = clientFluidToServer(count); } else { subType = count; } if (!player->hasShopItemForSale(it.id, subType)) { return; } merchant->onPlayerTrade(player, onBuy, it.id, subType, amount, ignoreCap, inBackpacks); } void Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreEquipped) { if (amount == 0 || amount > 100) { return; } Player *player = getPlayerByID(playerId); if (!player) { return; } int32_t onBuy, onSell; Npc *merchant = player->getShopOwner(onBuy, onSell); if (!merchant) { return; } const ItemType &it = Item::items.getItemIdByClientId(spriteId); if (it.id == 0) { return; } uint8_t subType; if (it.isSplash() || it.isFluidContainer()) { subType = clientFluidToServer(count); } else { subType = count; } merchant->onPlayerTrade(player, onSell, it.id, subType, amount, ignoreEquipped); } void Game::playerCloseShop(uint32_t playerId) { Player *player = getPlayerByID(playerId); if (!player) { return; } player->closeShopWindow(); } void Game::playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count) { Player *player = getPlayerByID(playerId); if (!player) { return; } int32_t onBuy, onSell; Npc *merchant = player->getShopOwner(onBuy, onSell); if (!merchant) { return; } const ItemType &it = Item::items.getItemIdByClientId(spriteId); if (it.id == 0) { return; } int32_t subType; if (it.isFluidContainer() || it.isSplash()) { subType = clientFluidToServer(count); } else { subType = count; } if (!player->hasShopItemForSale(it.id, subType)) { return; } if (!g_events->eventPlayerOnLookInShop(player, &it, subType)) { return; } std::ostringstream ss; ss << "You see " << Item::getDescription(it, 1, nullptr, subType); player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); } 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); // Rotate towards target immediately const Position &myPos = player->getPosition(); const Position &targetPos = attackCreature->getPosition(); int32_t diffX = targetPos.x - myPos.x; int32_t diffY = targetPos.y - myPos.y; Direction dir = DIRECTION_NONE; int32_t absX = std::abs(diffX); int32_t absY = std::abs(diffY); if (absX > 0 || absY > 0) { if (absX > absY * 2) { dir = (diffX > 0) ? DIRECTION_EAST : DIRECTION_WEST; } else if (absY > absX * 2) { dir = (diffY > 0) ? DIRECTION_SOUTH : DIRECTION_NORTH; } else { if (diffX > 0 && diffY < 0) dir = DIRECTION_NORTHEAST; else if (diffX > 0 && diffY > 0) dir = DIRECTION_SOUTHEAST; else if (diffX < 0 && diffY < 0) dir = DIRECTION_NORTHWEST; else if (diffX < 0 && diffY > 0) dir = DIRECTION_SOUTHWEST; } } if (dir != DIRECTION_NONE) { internalCreatureTurn(player, dir); } 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, bool 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::playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string &description, uint32_t icon, bool notify) { Player *player = getPlayerByID(playerId); if (!player) { return; } player->editVIP(guid, description, icon, notify); } 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::playerToggleOutfitExtension(uint32_t playerId, int mount, int, int, int, int) { Player *player = getPlayerByID(playerId); if (!player) { return; } if (mount != -1) player->toggleMount(mount == 1); } void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) { if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { return; } Player *player = getPlayerByID(playerId); if (!player) { return; } const Outfit *playerOutfit = Outfits::getInstance().getOutfitByLookType( player->getSex(), outfit.lookType); if (!playerOutfit) { outfit.lookMount = 0; outfit.lookWings = 0; outfit.lookAura = 0; outfit.lookShader = 0; outfit.lookRuneEffect = 0; } if (outfit.lookMount != 0) { Mount *mount = mounts.getMountByClientID(outfit.lookMount); if (!mount) { return; } if (!player->hasMount(mount)) { return; } if (player->isMounted()) { Mount *prevMount = mounts.getMountByID(player->getCurrentMount()); if (prevMount) { changeSpeed(player, mount->speed - prevMount->speed); } player->setCurrentMount(mount->id); } else { player->setCurrentMount(mount->id); outfit.lookMount = 0; } } else if (player->isMounted()) { player->dismount(); } if (outfit.lookWings != 0) { Wing *wing = wings.getWingByClientID(outfit.lookWings); if (!wing) { return; } if (!player->hasWing(wing)) { return; } } if (outfit.lookAura != 0) { Aura *aura = auras.getAuraByClientID(outfit.lookAura); if (!aura) { return; } if (!player->hasAura(aura)) { return; } } if (outfit.lookShader) { Shader *shader = shaders.getShaderByID(outfit.lookShader); if (!shader) { return; } if (!player->hasShader(shader)) { return; } } if (outfit.lookRuneEffect) { RuneEffect *runeEffect = runeEffects.getRuneEffectByID(outfit.lookRuneEffect); if (!runeEffect) { return; } if (!player->hasRuneEffect(runeEffect)) { return; } } if (player->canWear(outfit.lookType, outfit.lookAddons)) { player->defaultOutfit = outfit; // Reset the stored attackOutfit so getAttackOutfitLookType() will perform // a fresh lookup in Outfits.xml for the new outfit's attack type. // Without this, the old outfit's attackType would still be returned even // after a legitimate outfit change (e.g. changing from lookType 1544 to // 7415 would still show attackType 1543 instead of 7416). player->setAttackOutfitLookType(0); if (player->hasCondition(CONDITION_OUTFIT)) { return; } internalCreatureChangeOutfit(player, outfit); } } void Game::playerShowQuestLog(uint32_t playerId) { Player *player = getPlayerByID(playerId); if (!player) { return; } player->sendQuestLog(); } void Game::playerShowQuestLine(uint32_t playerId, uint16_t questId) { Player *player = getPlayerByID(playerId); if (!player) { return; } Quest *quest = quests.getQuestByID(questId); if (!quest) { return; } player->sendQuestLine(quest); } 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; } if (type != TALKTYPE_PRIVATE_PN) { 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_TO: case TALKTYPE_PRIVATE_RED_TO: playerSpeakTo(player, type, receiver, text); break; case TALKTYPE_CHANNEL_O: case TALKTYPE_CHANNEL_Y: case TALKTYPE_CHANNEL_R1: g_chat->talkToChannel(*player, type, text, channelId); break; case TALKTYPE_PRIVATE_PN: playerSpeakToNpc(player, text); break; case TALKTYPE_BROADCAST: playerBroadcastMessage(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) { if (!g_config.getBoolean(ConfigManager::EMOTE_SPELLS)) { return true; } else { return true; } } 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->hasCondition(CONDITION_YELLTICKS)) { player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); return false; } uint32_t minimumLevel = g_config.getNumber(ConfigManager::YELL_MINIMUM_LEVEL); if (player->getLevel() < minimumLevel) { std::ostringstream ss; ss << "You may not yell unless you have reached level " << minimumLevel; if (g_config.getBoolean(ConfigManager::YELL_ALLOW_PREMIUM)) { if (player->isPremium()) { internalCreatureSay(player, TALKTYPE_YELL, asUpperCaseString(text), false); return true; } else { ss << " or have a premium account"; } } ss << "."; player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); 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_TO && (player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER)) { type = TALKTYPE_PRIVATE_RED_FROM; } else { type = TALKTYPE_PRIVATE_FROM; } 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; } void Game::playerSpeakToNpc(Player *player, const std::string &text) { SpectatorVec spectators; map.getSpectators(spectators, player->getPosition()); for (Creature *spectator : spectators) { if (spectator->getNpc()) { spectator->onCreatureSay(player, TALKTYPE_PRIVATE_PN, text); } } } //-- 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, 18, 18, 14, 14); } } 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); if (creature != spectator) { g_events->eventCreatureOnHear(spectator, creature, text, type); } } 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) { int32_t varSpeed = creature->getSpeed() - creature->getBaseSpeed(); varSpeed += varSpeedDelta; creature->setSpeed(varSpeed); // 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; } if (creature->isAttackPulsing) { if (creature->attackOutfitEventId != 0) { g_scheduler.stopEvent(creature->attackOutfitEventId); creature->attackOutfitEventId = 0; } creature->isAttackPulsing = false; } 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.primary.type == COMBAT_NONE && damage.secondary.type == COMBAT_NONE) { return true; } if (target->getPlayer() && target->isInGhostMode()) { return true; } if (damage.primary.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: case COMBAT_ICEDAMAGE: case COMBAT_DEATHDAMAGE: { hitEffect = CONST_ME_BLOCKHIT; break; } case COMBAT_EARTHDAMAGE: { hitEffect = CONST_ME_GREEN_RINGS; break; } case COMBAT_HOLYDAMAGE: { hitEffect = CONST_ME_HOLYDAMAGE; break; } default: { hitEffect = CONST_ME_POFF; break; } } addMagicEffect(targetPos, hitEffect); } }; BlockType_t primaryBlockType, secondaryBlockType; if (damage.primary.type != COMBAT_NONE) { damage.primary.value = -damage.primary.value; primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field); damage.primary.value = -damage.primary.value; sendBlockEffect(primaryBlockType, damage.primary.type, target->getPosition()); } else { primaryBlockType = BLOCK_NONE; } if (damage.secondary.type != COMBAT_NONE) { damage.secondary.value = -damage.secondary.value; secondaryBlockType = target->blockHit(attacker, damage.secondary.type, damage.secondary.value, false, false, field); damage.secondary.value = -damage.secondary.value; sendBlockEffect(secondaryBlockType, damage.secondary.type, target->getPosition()); } else { secondaryBlockType = BLOCK_NONE; } damage.blockType = primaryBlockType; return (primaryBlockType != BLOCK_NONE) && (secondaryBlockType != 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; if (const Tile *tile = target->getTile()) { if (!tile->hasFlag(TILESTATE_PROTECTIONZONE)) { 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; case RACE_ENERGY: color = TEXTCOLOR_ELECTRICPURPLE; effect = CONST_ME_ENERGYHIT; 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_ELECTRICPURPLE; effect = CONST_ME_ENERGYHIT; break; } case COMBAT_EARTHDAMAGE: { color = TEXTCOLOR_LIGHTGREEN; effect = CONST_ME_GREEN_RINGS; break; } case COMBAT_DROWNDAMAGE: { color = TEXTCOLOR_LIGHTBLUE; effect = CONST_ME_LOSEENERGY; break; } case COMBAT_FIREDAMAGE: { color = TEXTCOLOR_ORANGE; effect = CONST_ME_HITBYFIRE; break; } case COMBAT_ICEDAMAGE: { color = TEXTCOLOR_SKYBLUE; effect = CONST_ME_ICEATTACK; break; } case COMBAT_HOLYDAMAGE: { color = TEXTCOLOR_YELLOW; effect = CONST_ME_HOLYDAMAGE; break; } case COMBAT_DEATHDAMAGE: { color = TEXTCOLOR_DARKRED; effect = CONST_ME_SMALLCLOUDS; break; } case COMBAT_LIFEDRAIN: { color = TEXTCOLOR_RED; effect = CONST_ME_MAGIC_RED; 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.primary.value > 0) { if (target->getHealth() <= 0) { return false; } Player *attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); } else { attackerPlayer = nullptr; } Player *targetPlayer = target->getPlayer(); if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { 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.primary.value); realHealthChange = target->getHealth() - realHealthChange; if (realHealthChange > 0 && !target->isInGhostMode()) { std::stringstream ss; ss << realHealthChange << (realHealthChange != 1 ? " hitpoints." : " hitpoint."); std::string damageString = ss.str(); std::string spectatorMessage; TextMessage message; message.position = targetPos; message.primary.value = realHealthChange; message.primary.color = TEXTCOLOR_PASTELRED; SpectatorVec spectators; map.getSpectators(spectators, targetPos, false, true); for (Creature *spectator : spectators) { Player *tmpPlayer = spectator->getPlayer(); if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { ss.str({}); ss << "You heal " << target->getNameDescription() << " for " << damageString; message.type = MESSAGE_HEALED; message.text = ss.str(); } else if (tmpPlayer == targetPlayer) { ss.str({}); if (!attacker) { ss << "You were healed"; } else if (targetPlayer == attackerPlayer) { ss << "You healed yourself"; } else { ss << "You were healed by " << attacker->getNameDescription(); } ss << " for " << damageString; message.type = MESSAGE_HEALED; message.text = ss.str(); } else { if (spectatorMessage.empty()) { ss.str({}); if (!attacker) { ss << ucfirst(target->getNameDescription()) << " was healed"; } else { ss << ucfirst(attacker->getNameDescription()) << " healed "; if (attacker == target) { ss << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "herself" : "himself") : "itself"); } else { ss << target->getNameDescription(); } } ss << " for " << damageString; spectatorMessage = ss.str(); } message.type = MESSAGE_HEALED_OTHERS; message.text = spectatorMessage; } tmpPlayer->sendTextMessage(message); } } } else { if (!target->isAttackable()) { if (!target->isInGhostMode()) { addMagicEffect(targetPos, CONST_ME_POFF); } return true; } Player *attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); } else { attackerPlayer = nullptr; } Player *targetPlayer = target->getPlayer(); if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { return false; } damage.primary.value = std::abs(damage.primary.value); damage.secondary.value = std::abs(damage.secondary.value); if (attacker && attacker->getPlayer()) { RuneEffect* activeEffect = g_game.runeEffects.getRuneEffectByID(attacker->getPlayer()->getCurrentRuneEffect()); if (activeEffect && activeEffect->bonusDamage > 0) { int32_t bonus = (damage.primary.value * activeEffect->bonusDamage) / 100; damage.primary.value += bonus; } } int32_t healthChange = damage.primary.value + damage.secondary.value; if (healthChange == 0) { return true; } TextMessage message; message.position = targetPos; SpectatorVec spectators; if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && damage.primary.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.primary.value + damage.secondary.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::stringstream ss; std::string damageString = std::to_string(manaDamage); std::string spectatorMessage; message.primary.value = manaDamage; message.primary.color = TEXTCOLOR_BLUE; for (Creature *spectator : spectators) { Player *tmpPlayer = spectator->getPlayer(); if (tmpPlayer->getPosition().z != targetPos.z) { continue; } if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { ss.str({}); ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana due to your attack."; message.type = MESSAGE_DAMAGE_DEALT; message.text = ss.str(); } else if (tmpPlayer == targetPlayer) { ss.str({}); ss << "You lose " << damageString << " mana"; if (!attacker) { ss << '.'; } else if (targetPlayer == attackerPlayer) { ss << " due to your own attack."; } else { ss << " due to an attack by " << attacker->getNameDescription() << '.'; } message.type = MESSAGE_DAMAGE_RECEIVED; message.text = ss.str(); } else { if (spectatorMessage.empty()) { ss.str({}); ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana"; if (attacker) { ss << " due to "; if (attacker == target) { ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack"); } else { ss << "an attack by " << attacker->getNameDescription(); } } ss << '.'; spectatorMessage = ss.str(); } message.type = MESSAGE_DAMAGE_OTHERS; message.text = spectatorMessage; } tmpPlayer->sendTextMessage(message); } damage.primary.value -= manaDamage; if (damage.primary.value < 0) { damage.secondary.value = std::max( 0, damage.secondary.value + damage.primary.value); damage.primary.value = 0; } } } int32_t realDamage = damage.primary.value + damage.secondary.value; if (realDamage == 0) { return true; } 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 targetHealth = target->getHealth(); if (damage.primary.value >= targetHealth) { damage.primary.value = targetHealth; damage.secondary.value = 0; } else if (damage.secondary.value) { damage.secondary.value = std::min( damage.secondary.value, targetHealth - damage.primary.value); } realDamage = damage.primary.value + damage.secondary.value; if (realDamage == 0) { return true; } if (spectators.empty()) { map.getSpectators(spectators, targetPos, true, true); } message.primary.value = damage.primary.value; message.secondary.value = damage.secondary.value; uint8_t hitEffect; if (message.primary.value) { combatGetTypeInfo(damage.primary.type, target, message.primary.color, hitEffect); if (hitEffect != CONST_ME_NONE) { addMagicEffect(spectators, targetPos, hitEffect); } } if (message.secondary.value) { combatGetTypeInfo(damage.secondary.type, target, message.secondary.color, hitEffect); if (hitEffect != CONST_ME_NONE) { addMagicEffect(spectators, targetPos, hitEffect); } } if (message.primary.color != TEXTCOLOR_NONE || message.secondary.color != TEXTCOLOR_NONE) { std::stringstream ss; ss << realDamage << (realDamage != 1 ? " hitpoints" : " hitpoint"); std::string damageString = ss.str(); std::string spectatorMessage; for (Creature *spectator : spectators) { Player *tmpPlayer = spectator->getPlayer(); if (tmpPlayer->getPosition().z != targetPos.z) { continue; } if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { ss.str({}); ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " due to your attack."; message.type = MESSAGE_DAMAGE_DEALT; message.text = ss.str(); } else if (tmpPlayer == targetPlayer) { ss.str({}); ss << "You lose " << damageString; if (!attacker) { ss << '.'; } else if (targetPlayer == attackerPlayer) { ss << " due to your own attack."; } else { ss << " due to an attack by " << attacker->getNameDescription() << '.'; } message.type = MESSAGE_DAMAGE_RECEIVED; message.text = ss.str(); } else { message.type = MESSAGE_DAMAGE_OTHERS; if (spectatorMessage.empty()) { ss.str({}); ss << ucfirst(target->getNameDescription()) << " loses " << damageString; if (attacker) { ss << " due to "; if (attacker == target) { if (targetPlayer) { ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack"); } else { ss << "its own attack"; } } else { ss << "an attack by " << attacker->getNameDescription(); } } ss << '.'; spectatorMessage = ss.str(); } message.text = spectatorMessage; } tmpPlayer->sendTextMessage(message); } } 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.primary.value + damage.secondary.value; if (manaChange > 0) { if (attacker) { const Player *attackerPlayer = attacker->getPlayer(); if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(target) == SKULL_NONE) { return false; } } 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); } } int32_t realManaChange = targetPlayer->getMana(); targetPlayer->changeMana(manaChange); realManaChange = targetPlayer->getMana() - realManaChange; if (realManaChange > 0 && !targetPlayer->isInGhostMode()) { TextMessage message(MESSAGE_HEALED, "You gained " + std::to_string(realManaChange) + " mana."); message.position = target->getPosition(); message.primary.value = realManaChange; message.primary.color = TEXTCOLOR_MAYABLUE; targetPlayer->sendTextMessage(message); } } 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; } if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { return false; } 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::stringstream ss; std::string damageString = std::to_string(manaLoss); std::string spectatorMessage; TextMessage message; message.position = targetPos; message.primary.value = manaLoss; message.primary.color = TEXTCOLOR_BLUE; SpectatorVec spectators; map.getSpectators(spectators, targetPos, false, true); for (Creature *spectator : spectators) { Player *tmpPlayer = spectator->getPlayer(); if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { ss.str({}); ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " mana due to your attack."; message.type = MESSAGE_DAMAGE_DEALT; message.text = ss.str(); } else if (tmpPlayer == targetPlayer) { ss.str({}); ss << "You lose " << damageString << " mana"; if (!attacker) { ss << '.'; } else if (targetPlayer == attackerPlayer) { ss << " due to your own attack."; } else { ss << " mana due to an attack by " << attacker->getNameDescription() << '.'; } message.type = MESSAGE_DAMAGE_RECEIVED; message.text = ss.str(); } else { if (spectatorMessage.empty()) { ss.str({}); ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " mana"; if (attacker) { ss << " due to "; if (attacker == target) { ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack"); } else { ss << "an attack by " << attacker->getNameDescription(); } } ss << '.'; spectatorMessage = ss.str(); } message.type = MESSAGE_DAMAGE_OTHERS; message.text = spectatorMessage; } tmpPlayer->sendTextMessage(message); } } 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, uint16_t effect) { SpectatorVec spectators; map.getSpectators(spectators, pos, true, true); addMagicEffect(spectators, pos, effect); } void Game::addMagicEffect(const SpectatorVec &spectators, const Position &pos, uint16_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, toPosSpectators; map.getSpectators(spectators, fromPos, false, true); map.getSpectators(toPosSpectators, toPos, false, true); spectators.addSpectators(toPosSpectators); 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::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, item->getDecayTo()); 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() { 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::updateCreatureWalkthrough(const Creature *creature) { // send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature *spectator : spectators) { Player *tmpPlayer = spectator->getPlayer(); tmpPlayer->sendCreatureWalkthrough(creature, tmpPlayer->canWalkthroughEx(creature)); } } 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::updatePlayerHelpers(const Player &player) { uint32_t creatureId = player.getID(); uint16_t helpers = player.getHelpers(); SpectatorVec spectators; map.getSpectators(spectators, player.getPosition(), true, true); for (Creature *spectator : spectators) { spectator->getPlayer()->sendCreatureHelpers(creatureId, helpers); } } void Game::updateCreatureType(Creature *creature) { const Player *masterPlayer = nullptr; uint32_t creatureId = creature->getID(); CreatureType_t creatureType = creature->getType(); if (creatureType == CREATURETYPE_MONSTER) { const Creature *master = creature->getMaster(); if (master) { masterPlayer = master->getPlayer(); if (masterPlayer) { creatureType = CREATURETYPE_SUMMON_OTHERS; } } } // send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); if (creatureType == CREATURETYPE_SUMMON_OTHERS) { for (Creature *spectator : spectators) { Player *player = spectator->getPlayer(); if (masterPlayer == player) { player->sendCreatureType(creatureId, CREATURETYPE_SUMMON_OWN); } else { player->sendCreatureType(creatureId, creatureType); } } } else { for (Creature *spectator : spectators) { spectator->getPlayer()->sendCreatureType(creatureId, creatureType); } } } 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.name << "!" << 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 (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) && player->getZone() != ZONE_PROTECTION)) { return; } party->setSharedExperience(player, sharedExpActive); } void Game::sendGuildMotd(uint32_t playerId) { Player *player = getPlayerByID(playerId); if (!player) { return; } Guild *guild = player->getGuild(); if (guild) { player->sendChannelMessage("Message of the Day", guild->getMotd(), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD); } } void Game::kickPlayer(uint32_t playerId, bool displayEffect) { Player *player = getPlayerByID(playerId); if (!player) { return; } player->kickPlayer(displayEffect); } void Game::playerReportRuleViolation(uint32_t playerId, const std::string &targetName, uint8_t reportType, uint8_t reportReason, const std::string &comment, const std::string &translation) { Player *player = getPlayerByID(playerId); if (!player) { return; } g_events->eventPlayerOnReportRuleViolation( player, targetName, reportType, reportReason, comment, translation); } void Game::playerReportBug(uint32_t playerId, const std::string &message, const Position &position, uint8_t category) { Player *player = getPlayerByID(playerId); if (!player) { return; } g_events->eventPlayerOnReportBug(player, message, position, category); } 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::playerLeaveMarket(uint32_t playerId) { Player *player = getPlayerByID(playerId); if (!player) { return; } player->setInMarket(false); } void Game::playerBrowseMarket(uint32_t playerId, uint16_t spriteId) { Player *player = getPlayerByID(playerId); if (!player) { return; } if (!player->isInMarket()) { return; } const ItemType &it = Item::items.getItemIdByClientId(spriteId); if (it.id == 0) { return; } if (it.wareId == 0) { return; } const MarketOfferList &buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id); const MarketOfferList &sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id); player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); player->sendMarketDetail(it.id); } void Game::playerBrowseMarketOwnOffers(uint32_t playerId) { Player *player = getPlayerByID(playerId); if (!player) { return; } if (!player->isInMarket()) { return; } const MarketOfferList &buyOffers = IOMarket::getOwnOffers(MARKETACTION_BUY, player->getGUID()); const MarketOfferList &sellOffers = IOMarket::getOwnOffers(MARKETACTION_SELL, player->getGUID()); player->sendMarketBrowseOwnOffers(buyOffers, sellOffers); } void Game::playerBrowseMarketOwnHistory(uint32_t playerId) { Player *player = getPlayerByID(playerId); if (!player) { return; } if (!player->isInMarket()) { return; } const HistoryMarketOfferList &buyOffers = IOMarket::getOwnHistory(MARKETACTION_BUY, player->getGUID()); const HistoryMarketOfferList &sellOffers = IOMarket::getOwnHistory(MARKETACTION_SELL, player->getGUID()); player->sendMarketBrowseOwnHistory(buyOffers, sellOffers); } void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous) { if (amount == 0 || amount > 64000) { return; } if (price == 0 || price > 999999999) { return; } if (type != MARKETACTION_BUY && type != MARKETACTION_SELL) { return; } Player *player = getPlayerByID(playerId); if (!player) { return; } if (!player->isInMarket()) { return; } if (g_config.getBoolean(ConfigManager::MARKET_PREMIUM) && !player->isPremium()) { player->sendMarketLeave(); return; } const ItemType &itt = Item::items.getItemIdByClientId(spriteId); if (itt.id == 0 || itt.wareId == 0) { return; } const ItemType &it = Item::items.getItemIdByClientId(itt.wareId); if (it.id == 0 || it.wareId == 0) { return; } if (!it.stackable && amount > 2000) { return; } const uint32_t maxOfferCount = g_config.getNumber(ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER); if (maxOfferCount != 0 && IOMarket::getPlayerOfferCount(player->getGUID()) >= maxOfferCount) { return; } uint64_t fee = (price / 100.) * amount; if (fee < 20) { fee = 20; } else if (fee > 1000) { fee = 1000; } if (type == MARKETACTION_SELL) { if (fee > player->bankBalance) { return; } DepotChest *depotChest = player->getDepotChest(player->getLastDepotId(), false); if (!depotChest) { return; } std::forward_list itemList = getMarketItemList(it.wareId, amount, depotChest, player->getInbox()); if (itemList.empty()) { return; } if (it.stackable) { uint16_t tmpAmount = amount; for (Item *item : itemList) { uint16_t removeCount = std::min(tmpAmount, item->getItemCount()); tmpAmount -= removeCount; internalRemoveItem(item, removeCount); if (tmpAmount == 0) { break; } } } else { for (Item *item : itemList) { internalRemoveItem(item); } } player->bankBalance -= fee; } else { uint64_t totalPrice = static_cast(price) * amount; totalPrice += fee; if (totalPrice > player->bankBalance) { return; } player->bankBalance -= totalPrice; } IOMarket::createOffer(player->getGUID(), static_cast(type), it.id, amount, price, anonymous); player->sendMarketEnter(player->getLastDepotId()); const MarketOfferList &buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id); const MarketOfferList &sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id); player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); } void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter) { Player *player = getPlayerByID(playerId); if (!player) { return; } if (!player->isInMarket()) { return; } MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter); if (offer.id == 0 || offer.playerId != player->getGUID()) { return; } if (offer.type == MARKETACTION_BUY) { player->bankBalance += static_cast(offer.price) * offer.amount; player->sendMarketEnter(player->getLastDepotId()); } else { const ItemType &it = Item::items[offer.itemId]; if (it.id == 0) { return; } if (it.stackable) { uint16_t tmpAmount = offer.amount; while (tmpAmount > 0) { int32_t stackCount = std::min(100, tmpAmount); Item *item = Item::CreateItem(it.id, stackCount); if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { delete item; break; } tmpAmount -= stackCount; } } else { int32_t subType; if (it.charges != 0) { subType = it.charges; } else { subType = -1; } for (uint16_t i = 0; i < offer.amount; ++i) { Item *item = Item::CreateItem(it.id, subType); if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { delete item; break; } } } } IOMarket::moveOfferToHistory(offer.id, OFFERSTATE_CANCELLED); offer.amount = 0; offer.timestamp += g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); player->sendMarketCancelOffer(offer); player->sendMarketEnter(player->getLastDepotId()); } void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount) { if (amount == 0 || amount > 64000) { return; } Player *player = getPlayerByID(playerId); if (!player) { return; } if (!player->isInMarket()) { return; } MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter); if (offer.id == 0) { return; } if (amount > offer.amount) { return; } const ItemType &it = Item::items[offer.itemId]; if (it.id == 0) { return; } uint64_t totalPrice = static_cast(offer.price) * amount; if (offer.type == MARKETACTION_BUY) { DepotChest *depotChest = player->getDepotChest(player->getLastDepotId(), false); if (!depotChest) { return; } std::forward_list itemList = getMarketItemList(it.wareId, amount, depotChest, player->getInbox()); if (itemList.empty()) { return; } Player *buyerPlayer = getPlayerByGUID(offer.playerId); if (!buyerPlayer) { buyerPlayer = new Player(nullptr); if (!IOLoginData::loadPlayerById(buyerPlayer, offer.playerId)) { delete buyerPlayer; return; } } if (it.stackable) { uint16_t tmpAmount = amount; for (Item *item : itemList) { uint16_t removeCount = std::min(tmpAmount, item->getItemCount()); tmpAmount -= removeCount; internalRemoveItem(item, removeCount); if (tmpAmount == 0) { break; } } } else { for (Item *item : itemList) { internalRemoveItem(item); } } player->bankBalance += totalPrice; if (it.stackable) { uint16_t tmpAmount = amount; while (tmpAmount > 0) { uint16_t stackCount = std::min(100, tmpAmount); Item *item = Item::CreateItem(it.id, stackCount); if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { delete item; break; } tmpAmount -= stackCount; } } else { int32_t subType; if (it.charges != 0) { subType = it.charges; } else { subType = -1; } for (uint16_t i = 0; i < amount; ++i) { Item *item = Item::CreateItem(it.id, subType); if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { delete item; break; } } } if (buyerPlayer->isOffline()) { IOLoginData::savePlayer(buyerPlayer); delete buyerPlayer; } else { buyerPlayer->onReceiveMail(); } } else { if (totalPrice > player->bankBalance) { return; } player->bankBalance -= totalPrice; if (it.stackable) { uint16_t tmpAmount = amount; while (tmpAmount > 0) { uint16_t stackCount = std::min(100, tmpAmount); Item *item = Item::CreateItem(it.id, stackCount); if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { delete item; break; } tmpAmount -= stackCount; } } else { int32_t subType; if (it.charges != 0) { subType = it.charges; } else { subType = -1; } for (uint16_t i = 0; i < amount; ++i) { Item *item = Item::CreateItem(it.id, subType); if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { delete item; break; } } } Player *sellerPlayer = getPlayerByGUID(offer.playerId); if (sellerPlayer) { sellerPlayer->bankBalance += totalPrice; } else { IOLoginData::increaseBankBalance(offer.playerId, totalPrice); } player->onReceiveMail(); } const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); IOMarket::appendHistory( player->getGUID(), (offer.type == MARKETACTION_BUY ? MARKETACTION_SELL : MARKETACTION_BUY), offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTEDEX); IOMarket::appendHistory(offer.playerId, offer.type, offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTED); offer.amount -= amount; if (offer.amount == 0) { IOMarket::deleteOffer(offer.id); } else { IOMarket::acceptOffer(offer.id, amount); } player->sendMarketEnter(player->getLastDepotId()); offer.timestamp += marketOfferDuration; player->sendMarketAcceptOffer(offer); } 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); } } std::forward_list Game::getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotChest *depotChest, Inbox *inbox) { std::forward_list itemList; uint16_t count = 0; std::list containers{depotChest, inbox}; do { Container *container = containers.front(); containers.pop_front(); for (Item *item : container->getItemList()) { Container *c = item->getContainer(); if (c && !c->empty()) { containers.push_back(c); continue; } const ItemType &itemType = Item::items[item->getID()]; if (itemType.wareId != wareId) { continue; } if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) { continue; } if (!item->hasMarketAttributes()) { continue; } itemList.push_front(item); count += Item::countByType(item, -1); if (count >= sufficientCount) { return itemList; } } } while (!containers.empty()); return std::forward_list(); } 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::sendOfflineTrainingDialog(Player *player) { if (!player) { return; } if (!player->hasModalWindowOpen(offlineTrainingWindow.id)) { player->sendModalWindow(offlineTrainingWindow); } } void Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice) { Player *player = getPlayerByID(playerId); if (!player) { return; } if (!player->hasModalWindowOpen(modalWindowId)) { return; } player->onModalWindowHandled(modalWindowId); // offline training, hardcoded if (modalWindowId == std::numeric_limits::max()) { if (button == 1) { if (choice == SKILL_SWORD || choice == SKILL_AXE || choice == SKILL_CLUB || choice == SKILL_DISTANCE || choice == SKILL_MAGLEVEL) { BedItem *bedItem = player->getBedItem(); if (bedItem && bedItem->sleep(player)) { player->setOfflineTrainingSkill(choice); return; } } } else { player->sendTextMessage(MESSAGE_EVENT_ADVANCE, "Offline training aborted."); } player->setBedItem(nullptr); } else { for (auto creatureEvent : player->getCreatureEvents(CREATURE_EVENT_MODALWINDOW)) { creatureEvent->executeModalWindow(player, modalWindowId, button, choice); } } } void Game::addPlayer(Player *player) { const std::string &lowercase_name = asLowerCaseString(player->getName()); mappedPlayerNames[lowercase_name] = player; mappedPlayerGuids[player->getGUID()] = 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); mappedPlayerGuids.erase(player->getGUID()); 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::decreaseBrowseFieldRef(const Position &pos) { Tile *tile = map.getTile(pos.x, pos.y, pos.z); if (!tile) { return; } auto it = browseFields.find(tile); if (it != browseFields.end()) { it->second->decrementReferenceCounter(); } } 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); } } Item *Game::getUniqueItem(uint16_t uniqueId) { auto it = uniqueItems.find(uniqueId); if (it == uniqueItems.end()) { return nullptr; } return it->second; } bool Game::addUniqueItem(uint16_t uniqueId, Item *item) { auto result = uniqueItems.emplace(uniqueId, item); if (!result.second) { std::cout << "Duplicate unique id: " << uniqueId << std::endl; } return result.second; } void Game::removeUniqueItem(uint16_t uniqueId) { auto it = uniqueItems.find(uniqueId); if (it != uniqueItems.end()) { uniqueItems.erase(it); } } bool Game::reload(ReloadTypes_t reloadType) { switch (reloadType) { case RELOAD_TYPE_ACTIONS: return g_actions->reload(); case RELOAD_TYPE_AURAS: return auras.reload(); case RELOAD_TYPE_CHAT: return g_chat->load(); case RELOAD_TYPE_CONFIG: return g_config.reload(); case RELOAD_TYPE_CREATURESCRIPTS: { g_creatureEvents->reload(); g_creatureEvents->removeInvalidEvents(); return true; } 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_MOUNTS: return mounts.reload(); case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload(); case RELOAD_TYPE_NPCS: { Npcs::reload(); return true; } case RELOAD_TYPE_QUESTS: return quests.reload(); case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup(); case RELOAD_TYPE_SHADERS: return shaders.reload(); case RELOAD_TYPE_RUNEEFFECTS: if (!runeEffects.reload()) { return false; } return true; case RELOAD_TYPE_OUTFITS: { if (!Outfits::getInstance().reload()) { return false; } for (const auto &it : players) { if (Player *player = it.second) { if (player->isRemoved()) { continue; } Outfit_t outfit = player->getCurrentOutfit(); const Outfit *outfitDef = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType); if (outfitDef) { for (int d = 0; d < OUTFIT_DIRECTIONS; ++d) { outfit.offsets[d] = outfitDef->offsets[d]; } } SpectatorVec spectators; map.getSpectators(spectators, player->getPosition(), true); for (Creature *spectator : spectators) { if (Player *tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureChangeOutfit(player, outfit); } } } } return true; } 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(); case RELOAD_TYPE_WEAPONS: { bool results = g_weapons->reload(); g_weapons->loadDefaults(); return results; } case RELOAD_TYPE_WINGS: return wings.reload(); case RELOAD_TYPE_SCRIPTS: { // commented out stuff is TODO, once we approach further in revscriptsys g_actions->clear(true); g_creatureEvents->clear(true); g_moveEvents->clear(true); g_talkActions->clear(true); g_globalEvents->clear(true); g_weapons->clear(true); g_weapons->loadDefaults(); g_spells->clear(true); g_scripts->loadScripts("scripts", false, true); g_creatureEvents->removeInvalidEvents(); /* Npcs::reload(); raids.reload() && raids.startup(); Item::items.reload(); quests.reload(); mounts.reload(); auras.reload(); wings.reload(); shaders.reload(); g_config.reload(); g_events->load(); g_chat->load(); */ return true; } default: { 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(); } 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_weapons->reload(); g_weapons->clear(true); g_weapons->loadDefaults(); quests.reload(); auras.reload(); mounts.reload(); wings.reload(); shaders.reload(); runeEffects.reload(); g_globalEvents->reload(); g_events->load(); g_chat->load(); g_actions->clear(true); g_creatureEvents->clear(true); g_moveEvents->clear(true); g_talkActions->clear(true); g_globalEvents->clear(true); g_spells->clear(true); g_scripts->loadScripts("scripts", false, true); g_creatureEvents->removeInvalidEvents(); return true; } } return true; }