//////////////////////////////////////////////////////////////////////// // OpenTibia - an opensource roleplaying game //////////////////////////////////////////////////////////////////////// // 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 3 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, see . //////////////////////////////////////////////////////////////////////// #include "otpch.h" #include "game.h" #include "configmanager.h" #ifdef __LOGIN_SERVER__ #include "gameservers.h" #endif #include "server.h" #include "chat.h" #include "luascript.h" #include "creature.h" #include "combat.h" #include "tile.h" #include "database.h" #include "iologindata.h" #include "ioban.h" #include "ioguild.h" #include "items.h" #include "container.h" #include "monsters.h" #include "house.h" #include "quests.h" #include "actions.h" #include "globalevent.h" #include "movement.h" #include "raids.h" #include "scriptmanager.h" #include "spells.h" #include "talkaction.h" #include "weapons.h" #include "vocation.h" #include "group.h" #ifdef __EXCEPTION_TRACER__ #include "exception.h" #endif extern ConfigManager g_config; extern Actions* g_actions; extern Monsters g_monsters; extern Npcs g_npcs; extern Chat g_chat; extern TalkActions* g_talkActions; extern Spells* g_spells; extern MoveEvents* g_moveEvents; extern Weapons* g_weapons; extern CreatureEvents* g_creatureEvents; extern GlobalEvents* g_globalEvents; Game::Game() { gameState = GAME_STATE_NORMAL; worldType = WORLD_TYPE_PVP; map = NULL; playersRecord = lastStageLevel = 0; for(int32_t i = 0; i < 3; i++) globalSaveMessage[i] = false; //(1440 minutes/day) * 10 seconds event interval / (3600 seconds/day) lightHourDelta = 1440 * 10 / 3600; lightHour = SUNRISE + (SUNSET - SUNRISE) / 2; lightLevel = LIGHT_LEVEL_DAY; lightState = LIGHT_STATE_DAY; lastBucket = checkCreatureLastIndex = checkLightEvent = checkCreatureEvent = checkDecayEvent = saveEvent = 0; #ifdef __WAR_SYSTEM__ checkWarsEvent = 0; #endif } Game::~Game() { blacklist.clear(); if(map) delete map; } void Game::start(ServiceManager* servicer) { checkDecayEvent = Scheduler::getInstance().addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, boost::bind(&Game::checkDecay, this))); checkCreatureEvent = Scheduler::getInstance().addEvent(createSchedulerTask(EVENT_CREATURE_THINK_INTERVAL, boost::bind(&Game::checkCreatures, this))); checkLightEvent = Scheduler::getInstance().addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, boost::bind(&Game::checkLight, this))); #ifdef __WAR_SYSTEM__ checkWarsEvent = Scheduler::getInstance().addEvent(createSchedulerTask(EVENT_WARSINTERVAL, boost::bind(&Game::checkWars, this))); #endif services = servicer; if(g_config.getBool(ConfigManager::GLOBALSAVE_ENABLED) && g_config.getNumber(ConfigManager::GLOBALSAVE_H) >= 1 && g_config.getNumber(ConfigManager::GLOBALSAVE_H) <= 24) { int32_t prepareGlobalSaveHour = g_config.getNumber(ConfigManager::GLOBALSAVE_H) - 1, hoursLeft = 0, minutesLeft = 0, minutesToRemove = 0; bool ignoreEvent = false; time_t timeNow = time(NULL); const tm* theTime = localtime(&timeNow); if(theTime->tm_hour > prepareGlobalSaveHour) { hoursLeft = 24 - (theTime->tm_hour - prepareGlobalSaveHour); if(theTime->tm_min > 55 && theTime->tm_min <= 59) minutesToRemove = theTime->tm_min - 55; else minutesLeft = 55 - theTime->tm_min; } else if(theTime->tm_hour == prepareGlobalSaveHour) { if(theTime->tm_min >= 55 && theTime->tm_min <= 59) { if(theTime->tm_min >= 57) setGlobalSaveMessage(0, true); if(theTime->tm_min == 59) setGlobalSaveMessage(1, true); prepareGlobalSave(); ignoreEvent = true; } else minutesLeft = 55 - theTime->tm_min; } else { hoursLeft = prepareGlobalSaveHour - theTime->tm_hour; if(theTime->tm_min > 55 && theTime->tm_min <= 59) minutesToRemove = theTime->tm_min - 55; else minutesLeft = 55 - theTime->tm_min; } uint32_t hoursLeftInMs = 60000 * 60 * hoursLeft, minutesLeftInMs = 60000 * (minutesLeft - minutesToRemove); if(!ignoreEvent && (hoursLeftInMs + minutesLeftInMs) > 0) saveEvent = Scheduler::getInstance().addEvent(createSchedulerTask(hoursLeftInMs + minutesLeftInMs, boost::bind(&Game::prepareGlobalSave, this))); } } void Game::loadGameState() { ScriptEnviroment::loadGameState(); loadMotd(); loadPlayersRecord(); checkHighscores(); } void Game::setGameState(GameState_t newState) { if(gameState == GAME_STATE_SHUTDOWN) return; //this cannot be stopped if(gameState != newState) { gameState = newState; switch(newState) { case GAME_STATE_INIT: { Spawns::getInstance()->startup(); Raids::getInstance()->loadFromXml(); Raids::getInstance()->startup(); Quests::getInstance()->loadFromXml(); loadGameState(); g_globalEvents->startup(); IOBan::getInstance()->clearTemporials(); if(g_config.getBool(ConfigManager::REMOVE_PREMIUM_ON_INIT)) IOLoginData::getInstance()->updatePremiumDays(); #ifdef __WAR_SYSTEM__ IOGuild::getInstance()->checkWars(); #endif break; } case GAME_STATE_SHUTDOWN: { g_globalEvents->execute(GLOBAL_EVENT_SHUTDOWN); AutoList::iterator it = Player::autoList.begin(); while(it != Player::autoList.end()) //kick all players that are still online { it->second->kickPlayer(true, true); it = Player::autoList.begin(); } Houses::getInstance()->payHouses(); saveGameState(false); Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::shutdown, this))); Scheduler::getInstance().stop(); Dispatcher::getInstance().stop(); break; } case GAME_STATE_CLOSED: { AutoList::iterator it = Player::autoList.begin(); while(it != Player::autoList.end()) //kick all players who not allowed to stay { if(!it->second->hasFlag(PlayerFlag_CanAlwaysLogin)) { it->second->kickPlayer(true, true); it = Player::autoList.begin(); } else ++it; } saveGameState(false); break; } case GAME_STATE_NORMAL: case GAME_STATE_MAINTAIN: case GAME_STATE_STARTUP: case GAME_STATE_CLOSING: default: break; } } } void Game::saveGameState(bool shallow) { std::cout << "> Saving server..." << std::endl; uint64_t start = OTSYS_TIME(); if(gameState == GAME_STATE_NORMAL) setGameState(GAME_STATE_MAINTAIN); IOLoginData* io = IOLoginData::getInstance(); for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) { it->second->loginPosition = it->second->getPosition(); io->savePlayer(it->second, false, shallow); } std::string storage = "relational"; if(g_config.getBool(ConfigManager::HOUSE_STORAGE)) storage = "binary"; map->saveMap(); ScriptEnviroment::saveGameState(); if(gameState == GAME_STATE_MAINTAIN) setGameState(GAME_STATE_NORMAL); std::cout << "> SAVE: Complete in " << (OTSYS_TIME() - start) / (1000.) << " seconds using " << storage << " house storage." << std::endl; } int32_t Game::loadMap(std::string filename) { if(!map) map = new Map; return map->loadMap(getFilePath(FILE_TYPE_OTHER, std::string("world/" + filename + ".otbm"))); } void Game::cleanMap(uint32_t& count) { uint64_t start = OTSYS_TIME(); uint32_t tiles = 0; count = 0; int32_t marked = -1; if(gameState == GAME_STATE_NORMAL) setGameState(GAME_STATE_MAINTAIN); Tile* tile = NULL; ItemVector::iterator tit; if(g_config.getBool(ConfigManager::STORE_TRASH)) { marked = trash.size(); Trash::iterator it = trash.begin(); if(g_config.getBool(ConfigManager::CLEAN_PROTECTED_ZONES)) { for(; it != trash.end(); ++it) { if(!(tile = getTile(*it))) continue; tile->resetFlag(TILESTATE_TRASHED); if(tile->hasFlag(TILESTATE_HOUSE) || !tile->getItemList()) continue; ++tiles; tit = tile->getItemList()->begin(); while(tile->getItemList() && tit != tile->getItemList()->end()) { if((*tit)->isMoveable() && !(*tit)->isLoadedFromMap() && !(*tit)->isScriptProtected()) { internalRemoveItem(NULL, *tit); if(tile->getItemList()) tit = tile->getItemList()->begin(); ++count; } else ++tit; } } trash.clear(); } else { for(; it != trash.end(); ++it) { if(!(tile = getTile(*it))) continue; tile->resetFlag(TILESTATE_TRASHED); if(tile->hasFlag(TILESTATE_PROTECTIONZONE) || !tile->getItemList()) continue; ++tiles; tit = tile->getItemList()->begin(); while(tile->getItemList() && tit != tile->getItemList()->end()) { if((*tit)->isMoveable() && !(*tit)->isLoadedFromMap() && !(*tit)->isScriptProtected()) { internalRemoveItem(NULL, *tit); if(tile->getItemList()) tit = tile->getItemList()->begin(); ++count; } else ++tit; } } trash.clear(); } } else if(g_config.getBool(ConfigManager::CLEAN_PROTECTED_ZONES)) { for(uint16_t z = 0; z < (uint16_t)MAP_MAX_LAYERS; z++) { for(uint16_t y = 1; y <= map->mapHeight; y++) { for(uint16_t x = 1; x <= map->mapWidth; x++) { if(!(tile = getTile(x, y, z)) || tile->hasFlag(TILESTATE_HOUSE) || !tile->getItemList()) continue; ++tiles; tit = tile->getItemList()->begin(); while(tile->getItemList() && tit != tile->getItemList()->end()) { if((*tit)->isMoveable() && !(*tit)->isLoadedFromMap() && !(*tit)->isScriptProtected()) { internalRemoveItem(NULL, *tit); if(tile->getItemList()) tit = tile->getItemList()->begin(); ++count; } else ++tit; } } } } } else { for(uint16_t z = 0; z < (uint16_t)MAP_MAX_LAYERS; z++) { for(uint16_t y = 1; y <= map->mapHeight; y++) { for(uint16_t x = 1; x <= map->mapWidth; x++) { if(!(tile = getTile(x, y, z)) || tile->hasFlag(TILESTATE_PROTECTIONZONE) || !tile->getItemList()) continue; ++tiles; tit = tile->getItemList()->begin(); while(tile->getItemList() && tit != tile->getItemList()->end()) { if((*tit)->isMoveable() && !(*tit)->isLoadedFromMap() && !(*tit)->isScriptProtected()) { internalRemoveItem(NULL, *tit); if(tile->getItemList()) tit = tile->getItemList()->begin(); ++count; } else ++tit; } } } } } if(gameState == GAME_STATE_MAINTAIN) setGameState(GAME_STATE_NORMAL); std::cout << "> CLEAN: Removed " << count << " item" << (count != 1 ? "s" : "") << " from " << tiles << " tile" << (tiles != 1 ? "s" : ""); if(marked >= 0) std::cout << " (" << marked << " were marked)"; std::cout << " in " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; } void Game::proceduralRefresh(RefreshTiles::iterator* it/* = NULL*/) { if(!it) it = new RefreshTiles::iterator(refreshTiles.begin()); // Refresh 250 tiles each cycle refreshMap(it, 250); if((*it) != refreshTiles.end()) { delete it; return; } // Refresh some items every 100 ms until all tiles has been checked // For 100k tiles, this would take 100000/2500 = 40s = half a minute Scheduler::getInstance().addEvent(createSchedulerTask(100, boost::bind(&Game::proceduralRefresh, this, it))); } void Game::refreshMap(RefreshTiles::iterator* it/* = NULL*/, uint32_t limit/* = 0*/) { RefreshTiles::iterator end = refreshTiles.end(); if(!it) { RefreshTiles::iterator begin = refreshTiles.begin(); it = &begin; } Tile* tile = NULL; TileItemVector* items = NULL; Item* item = NULL; for(uint32_t cleaned = 0, downItemsSize = 0; (*it) != end && (limit ? (cleaned < limit) : true); ++(*it), ++cleaned) { if(!(tile = (*it)->first)) continue; if((items = tile->getItemList())) { downItemsSize = tile->getDownItemCount(); for(uint32_t i = downItemsSize - 1; i >= 0; --i) { if((item = items->at(i))) { #ifndef __DEBUG__ internalRemoveItem(NULL, item); #else if(internalRemoveItem(NULL, item) != RET_NOERROR) { std::cout << "> WARNING: Could not refresh item: " << item->getID(); std::cout << " at position: " << tile->getPosition() << std::endl; } #endif } } } cleanup(); TileItemVector list = (*it)->second.list; for(ItemVector::reverse_iterator it = list.rbegin(); it != list.rend(); ++it) { Item* item = (*it)->clone(); if(internalAddItem(NULL, tile, item , INDEX_WHEREEVER, FLAG_NOLIMIT) == RET_NOERROR) { if(item->getUniqueId() != 0) ScriptEnviroment::addUniqueThing(item); startDecay(item); } else { std::cout << "> WARNING: Could not refresh item: " << item->getID() << " at position: " << tile->getPosition() << std::endl; delete item; } } } } Cylinder* Game::internalGetCylinder(Player* player, const Position& pos) { if(pos.x != 0xFFFF) return getTile(pos); //container if(pos.y & 0x40) { uint8_t fromCid = pos.y & 0x0F; return player->getContainer(fromCid); } return player; } Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index, uint32_t spriteId/* = 0*/, stackposType_t type/* = STACKPOS_NORMAL*/) { if(pos.x != 0xFFFF) { Tile* tile = getTile(pos); if(!tile) return NULL; if(type == STACKPOS_LOOK) return tile->getTopVisibleThing(player); Thing* thing = NULL; switch(type) { case STACKPOS_MOVE: { Item* item = tile->getTopDownItem(); if(item && item->isMoveable()) thing = item; else thing = tile->getTopVisibleCreature(player); break; } case STACKPOS_USE: { thing = tile->getTopDownItem(); break; } case STACKPOS_USEITEM: { Item* downItem = tile->getTopDownItem(); Item* item = tile->getItemByTopOrder(2); if(item && g_actions->hasAction(item)) { const ItemType& it = Item::items[item->getID()]; if(!downItem || (!it.hasHeight && !it.allowPickupable)) thing = item; } if(!thing) thing = downItem; if(!thing) thing = tile->getTopTopItem(); if(!thing) thing = tile->ground; break; } default: thing = tile->__getThing(index); break; } if(player && thing && thing->getItem()) { if(tile->hasProperty(ISVERTICAL)) { if(player->getPosition().x + 1 == tile->getPosition().x) thing = NULL; } else if(tile->hasProperty(ISHORIZONTAL) && player->getPosition().y + 1 == tile->getPosition().y) thing = NULL; } return thing; } else if(pos.y & 0x40) { uint8_t fromCid = pos.y & 0x0F, slot = pos.z; if(Container* parentcontainer = player->getContainer(fromCid)) return parentcontainer->getItem(slot); } else if(!pos.y && !pos.z) { const ItemType& it = Item::items.getItemIdByClientId(spriteId); if(!it.id) return NULL; int32_t subType = -1; if(it.isFluidContainer() && index < int32_t(sizeof(reverseFluidMap) / sizeof(int8_t))) subType = reverseFluidMap[index]; return findItemOfType(player, it.id, true, subType); } return player->getInventoryItem((slots_t)static_cast(pos.y)); } void Game::internalGetPosition(Item* item, Position& pos, int16_t& stackpos) { pos.x = pos.y = pos.z = stackpos = 0; if(Cylinder* topParent = item->getTopParent()) { if(Player* player = dynamic_cast(topParent)) { pos.x = 0xFFFF; Container* container = dynamic_cast(item->getParent()); if(container) { pos.y = ((uint16_t) ((uint16_t)0x40) | ((uint16_t)player->getContainerID(container)) ); pos.z = container->__getIndexOfThing(item); stackpos = pos.z; } else { pos.y = player->__getIndexOfThing(item); stackpos = pos.y; } } else if(Tile* tile = topParent->getTile()) { pos = tile->getPosition(); stackpos = tile->__getIndexOfThing(item); } } } Creature* Game::getCreatureByID(uint32_t id) { if(!id) return NULL; AutoList::iterator it = autoList.find(id); if(it != autoList.end() && !it->second->isRemoved()) return it->second; return NULL; //just in case the player doesnt exist } Player* Game::getPlayerByID(uint32_t id) { if(!id) return NULL; AutoList::iterator it = Player::autoList.find(id); if(it != Player::autoList.end() && !it->second->isRemoved()) return it->second; return NULL; //just in case the player doesnt exist } Creature* Game::getCreatureByName(std::string s) { if(s.empty()) return NULL; toLowerCaseString(s); for(AutoList::iterator it = autoList.begin(); it != autoList.end(); ++it) { if(!it->second->isRemoved() && asLowerCaseString(it->second->getName()) == s) return it->second; } return NULL; //just in case the creature doesnt exist } Player* Game::getPlayerByName(std::string s) { if(s.empty()) return NULL; toLowerCaseString(s); for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) { if(!it->second->isRemoved() && asLowerCaseString(it->second->getName()) == s) return it->second; } return NULL; } Player* Game::getPlayerByNameEx(const std::string& s) { Player* player = getPlayerByName(s); if(player) return player; std::string name = s; if(!IOLoginData::getInstance()->playerExists(name)) return NULL; player = new Player(name, NULL); if(IOLoginData::getInstance()->loadPlayer(player, name)) return player; #ifdef __DEBUG__ std::cout << "[Failure - Game::getPlayerByNameEx] Cannot load player: " << name << std::endl; #endif delete player; return NULL; } Player* Game::getPlayerByGuid(uint32_t guid) { if(!guid) return NULL; for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) { if(!it->second->isRemoved() && it->second->getGUID() == guid) return it->second; } return NULL; } Player* Game::getPlayerByGuidEx(uint32_t guid) { Player* player = getPlayerByGuid(guid); if(player) return player; std::string name; if(!IOLoginData::getInstance()->getNameByGuid(guid, name)) return NULL; player = new Player(name, NULL); if(IOLoginData::getInstance()->loadPlayer(player, name)) return player; #ifdef __DEBUG__ std::cout << "[Failure - Game::getPlayerByGuidEx] Cannot load player: " << name << std::endl; #endif delete player; return NULL; } ReturnValue Game::getPlayerByNameWildcard(std::string s, Player*& player) { player = NULL; if(s.empty()) return RET_PLAYERWITHTHISNAMEISNOTONLINE; char tmp = *s.rbegin(); if(tmp != '~' && tmp != '*') { player = getPlayerByName(s); if(!player) return RET_PLAYERWITHTHISNAMEISNOTONLINE; return RET_NOERROR; } Player* last = NULL; s = s.substr(0, s.length() - 1); toLowerCaseString(s); for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) { if(it->second->isRemoved()) continue; std::string name = asLowerCaseString(it->second->getName()); if(name.substr(0, s.length()) != s) continue; if(last) return RET_NAMEISTOOAMBIGUOUS; last = it->second; } if(!last) return RET_PLAYERWITHTHISNAMEISNOTONLINE; player = last; return RET_NOERROR; } Player* Game::getPlayerByAccount(uint32_t acc) { for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) { if(!it->second->isRemoved() && it->second->getAccount() == acc) return it->second; } return NULL; } PlayerVector Game::getPlayersByName(std::string s) { toLowerCaseString(s); PlayerVector players; for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) { if(!it->second->isRemoved() && asLowerCaseString(it->second->getName()) == s) players.push_back(it->second); } return players; } PlayerVector Game::getPlayersByAccount(uint32_t acc) { PlayerVector players; for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) { if(!it->second->isRemoved() && it->second->getAccount() == acc) players.push_back(it->second); } return players; } PlayerVector Game::getPlayersByIP(uint32_t ip, uint32_t mask) { PlayerVector players; for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) { if(!it->second->isRemoved() && (it->second->getIP() & mask) == (ip & mask)) players.push_back(it->second); } return players; } bool Game::internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos /*= false*/, bool forced /*= false*/) { if(creature->getParent()) return false; if(!map->placeCreature(pos, creature, extendedPos, forced)) return false; creature->addRef(); creature->setID(); autoList[creature->getID()] = creature; creature->addList(); return true; } bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedPos /*= false*/, bool forced /*= false*/) { Player* tmpPlayer = NULL; if((tmpPlayer = creature->getPlayer()) && !tmpPlayer->storedConditionList.empty()) { for(ConditionList::iterator it = tmpPlayer->storedConditionList.begin(); it != tmpPlayer->storedConditionList.end(); ++it) { if((*it)->getType() == CONDITION_MUTED && ((*it)->getTicks() - ( (time(NULL) - tmpPlayer->getLastLogout()) * 1000)) <= 0) continue; tmpPlayer->addCondition(*it); } tmpPlayer->storedConditionList.clear(); } if(!internalPlaceCreature(creature, pos, extendedPos, forced)) return false; SpectatorVec::iterator it; SpectatorVec list; getSpectators(list, creature->getPosition(), false, true); for(it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendCreatureAppear(creature); } for(it = list.begin(); it != list.end(); ++it) (*it)->onCreatureAppear(creature); creature->setLastPosition(pos); creature->getParent()->postAddNotification(NULL, creature, NULL, creature->getParent()->__getIndexOfThing(creature)); addCreatureCheck(creature); creature->onPlacedCreature(); return true; } ReturnValue Game::placeSummon(Creature* creature, const std::string& name) { Monster* monster = Monster::createMonster(name); if(!monster) return RET_NOTPOSSIBLE; // Place the monster creature->addSummon(monster); if(placeCreature(monster, creature->getPosition(), true)) return RET_NOERROR; creature->removeSummon(monster); return RET_NOTENOUGHROOM; } bool Game::removeCreature(Creature* creature, bool isLogout /*= true*/) { if(creature->isRemoved()) return false; Tile* tile = creature->getTile(); SpectatorVec list; SpectatorVec::iterator it; getSpectators(list, tile->getPosition(), false, true); Player* player = NULL; std::vector oldStackPosVector; for(it = list.begin(); it != list.end(); ++it) { if((player = (*it)->getPlayer()) && player->canSeeCreature(creature)) oldStackPosVector.push_back(tile->getClientIndexOfThing(player, creature)); } int32_t oldIndex = tile->__getIndexOfThing(creature); if(!map->removeCreature(creature)) return false; //send to client uint32_t i = 0; for(it = list.begin(); it != list.end(); ++it) { if(!(player = (*it)->getPlayer()) || !player->canSeeCreature(creature)) continue; player->sendCreatureDisappear(creature, oldStackPosVector[i]); ++i; } //event method for(it = list.begin(); it != list.end(); ++it) (*it)->onCreatureDisappear(creature, isLogout); creature->getParent()->postRemoveNotification(NULL, creature, NULL, oldIndex, true); creature->onRemovedCreature(); autoList.erase(creature->getID()); freeThing(creature); removeCreatureCheck(creature); for(std::list::iterator it = creature->summons.begin(); it != creature->summons.end(); ++it) removeCreature(*it); return true; } bool Game::playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t spriteId, int16_t fromStackpos, const Position& toPos, uint8_t count) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; uint8_t fromIndex = 0; if(fromPos.x == 0xFFFF) { if(fromPos.y & 0x40) fromIndex = static_cast(fromPos.z); else fromIndex = static_cast(fromPos.y); } else fromIndex = fromStackpos; Thing* thing = internalGetThing(player, fromPos, fromIndex, spriteId, STACKPOS_MOVE); Cylinder* toCylinder = internalGetCylinder(player, toPos); if(!thing || !toCylinder) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } if(Creature* movingCreature = thing->getCreature()) { uint32_t delay = g_config.getNumber(ConfigManager::PUSH_CREATURE_DELAY); if(Position::areInRange<1,1,0>(movingCreature->getPosition(), player->getPosition()) && delay > 0) { SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerMoveCreature, this, player->getID(), movingCreature->getID(), movingCreature->getPosition(), toCylinder->getPosition())); player->setNextActionTask(task); } else playerMoveCreature(playerId, movingCreature->getID(), movingCreature->getPosition(), toCylinder->getPosition()); } else if(thing->getItem()) playerMoveItem(playerId, fromPos, spriteId, fromStackpos, toPos, count); return true; } bool Game::playerMoveCreature(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreaturePos, const Position& toPos) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved() || player->hasFlag(PlayerFlag_CannotMoveCreatures)) return false; if(!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerMoveCreature, this, playerId, movingCreatureId, movingCreaturePos, toPos)); player->setNextActionTask(task); return false; } Creature* movingCreature = getCreatureByID(movingCreatureId); if(!movingCreature || movingCreature->isRemoved() || movingCreature->getNoMove()) return false; player->setNextActionTask(NULL); if(!Position::areInRange<1,1,0>(movingCreaturePos, player->getPosition()) && !player->hasCustomFlag(PlayerCustomFlag_CanMoveFromFar)) { //need to walk to the creature first before moving it std::list listDir; if(getPathToEx(player, movingCreaturePos, listDir, 0, 1, true, true)) { Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(player->getStepDuration(), boost::bind(&Game::playerMoveCreature, this, playerId, movingCreatureId, movingCreaturePos, toPos)); player->setNextWalkActionTask(task); return true; } player->sendCancelMessage(RET_THEREISNOWAY); return false; } Tile* toTile = map->getTile(toPos); if(!toTile) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } if((!movingCreature->isPushable() && !player->hasFlag(PlayerFlag_CanPushAllCreatures)) || !player->canSeeCreature(movingCreature)) { player->sendCancelMessage(RET_NOTMOVEABLE); return false; } //check throw distance const Position& pos = movingCreature->getPosition(); if(!player->hasCustomFlag(PlayerCustomFlag_CanThrowAnywhere) && ((std::abs(pos.x - toPos.x) > movingCreature->getThrowRange()) || (std::abs( pos.y - toPos.y) > movingCreature->getThrowRange()) || (std::abs( pos.z - toPos.z) * 4 > movingCreature->getThrowRange()))) { player->sendCancelMessage(RET_DESTINATIONOUTOFREACH); return false; } if(player != movingCreature) { if(toTile->hasProperty(BLOCKPATH)) { player->sendCancelMessage(RET_NOTENOUGHROOM); return false; } if((movingCreature->getZone() == ZONE_PROTECTION || movingCreature->getZone() == ZONE_NOPVP) && !toTile->hasFlag(TILESTATE_NOPVPZONE) && !toTile->hasFlag(TILESTATE_PROTECTIONZONE) && !player->hasFlag(PlayerFlag_IgnoreProtectionZone)) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } if(toTile->getCreatures() && !toTile->getCreatures()->empty() && !player->hasFlag(PlayerFlag_CanPushAllCreatures)) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } } bool deny = false; CreatureEventList pushEvents = player->getCreatureEvents(CREATURE_EVENT_PUSH); for(CreatureEventList::iterator it = pushEvents.begin(); it != pushEvents.end(); ++it) { if(!(*it)->executePush(player, movingCreature) && !deny) deny = true; } if(deny) return false; ReturnValue ret = internalMoveCreature(player, movingCreature, movingCreature->getTile(), toTile); if(ret != RET_NOERROR) { if(!player->hasCustomFlag(PlayerCustomFlag_CanMoveFromFar) || !player->hasCustomFlag(PlayerCustomFlag_CanMoveAnywhere)) { player->sendCancelMessage(ret); return false; } if(!toTile->ground) { player->sendCancelMessage(RET_NOTPOSSIBLE); return true; } internalTeleport(movingCreature, toTile->getPosition(), true); return true; } if(Player* movingPlayer = movingCreature->getPlayer()) { uint64_t delay = OTSYS_TIME() + movingPlayer->getStepDuration(); if(delay > movingPlayer->getNextActionTime()) movingPlayer->setNextAction(delay); } return true; } ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, uint32_t flags/* = 0*/) { const Position& currentPos = creature->getPosition(); Cylinder* fromTile = creature->getTile(); Cylinder* toTile = NULL; Position destPos = getNextPosition(direction, currentPos); if(direction < SOUTHWEST && creature->getPlayer()) { Tile* tmpTile = NULL; if(currentPos.z != 8 && creature->getTile()->hasHeight(3)) //try go up { if((!(tmpTile = map->getTile(Position(currentPos.x, currentPos.y, currentPos.z - 1))) || (!tmpTile->ground && !tmpTile->hasProperty(BLOCKSOLID))) && (tmpTile = map->getTile(Position(destPos.x, destPos.y, destPos.z - 1))) && tmpTile->ground && !tmpTile->hasProperty(BLOCKSOLID)) { flags = flags | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; destPos.z--; } } else if(currentPos.z != 7 && (!(tmpTile = map->getTile(destPos)) || (!tmpTile->ground && !tmpTile->hasProperty(BLOCKSOLID))) && (tmpTile = map->getTile(Position( destPos.x, destPos.y, destPos.z + 1))) && tmpTile->hasHeight(3)) //try go down { flags = flags | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; destPos.z++; } } ReturnValue ret = RET_NOTPOSSIBLE; if((toTile = map->getTile(destPos))) ret = internalMoveCreature(NULL, creature, fromTile, toTile, flags); if(ret != RET_NOERROR) { if(Player* player = creature->getPlayer()) { player->sendCancelMessage(ret); player->sendCancelWalk(); } } return ret; } ReturnValue Game::internalMoveCreature(Creature* actor, Creature* creature, Cylinder* fromCylinder, Cylinder* toCylinder, uint32_t flags/* = 0*/) { //check if we can move the creature to the destination ReturnValue ret = toCylinder->__queryAdd(0, creature, 1, flags); if(ret != RET_NOERROR) return ret; fromCylinder->getTile()->moveCreature(actor, creature, toCylinder); if(creature->getParent() != toCylinder) return RET_NOERROR; Item* toItem = NULL; Cylinder* subCylinder = NULL; int32_t n = 0, tmp = 0; while((subCylinder = toCylinder->__queryDestination(tmp, creature, &toItem, flags)) != toCylinder) { toCylinder->getTile()->moveCreature(actor, creature, subCylinder); if(creature->getParent() != subCylinder) //could happen if a script move the creature break; toCylinder = subCylinder; flags = 0; if(++n >= MAP_MAX_LAYERS) //to prevent infinite loop break; } return RET_NOERROR; } bool Game::playerMoveItem(uint32_t playerId, const Position& fromPos, uint16_t spriteId, int16_t fromStackpos, const Position& toPos, uint8_t count) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved() || player->hasFlag(PlayerFlag_CannotMoveItems)) return false; if(!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerMoveItem, this, playerId, fromPos, spriteId, fromStackpos, toPos, count)); player->setNextActionTask(task); return false; } player->setNextActionTask(NULL); Cylinder* fromCylinder = internalGetCylinder(player, fromPos); uint8_t fromIndex = 0; if(fromPos.x == 0xFFFF) { if(fromPos.y & 0x40) fromIndex = static_cast(fromPos.z); else fromIndex = static_cast(fromPos.y); } else fromIndex = fromStackpos; Thing* thing = internalGetThing(player, fromPos, fromIndex, spriteId, STACKPOS_MOVE); if(!thing || !thing->getItem()) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } Item* item = thing->getItem(); Cylinder* toCylinder = internalGetCylinder(player, toPos); uint8_t toIndex = 0; if(toPos.x == 0xFFFF) { if(toPos.y & 0x40) toIndex = static_cast(toPos.z); else toIndex = static_cast(toPos.y); } if(!fromCylinder || !toCylinder || !item || item->getClientID() != spriteId) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } if(!player->hasCustomFlag(PlayerCustomFlag_CanPushAllItems) && (!item->isPushable() || (item->isLoadedFromMap() && (item->getUniqueId() != 0 || (item->getActionId() != 0 && item->getContainer()))))) { player->sendCancelMessage(RET_NOTMOVEABLE); return false; } const Position& mapFromPos = fromCylinder->getTile()->getPosition(); const Position& mapToPos = toCylinder->getTile()->getPosition(); const Position& playerPos = player->getPosition(); if(playerPos.z > mapFromPos.z && !player->hasCustomFlag(PlayerCustomFlag_CanThrowAnywhere)) { player->sendCancelMessage(RET_FIRSTGOUPSTAIRS); return false; } if(playerPos.z < mapFromPos.z && !player->hasCustomFlag(PlayerCustomFlag_CanThrowAnywhere)) { player->sendCancelMessage(RET_FIRSTGODOWNSTAIRS); return false; } if(!Position::areInRange<1,1,0>(playerPos, mapFromPos) && !player->hasCustomFlag(PlayerCustomFlag_CanMoveFromFar)) { //need to walk to the item first before using it std::list listDir; if(getPathToEx(player, item->getPosition(), listDir, 0, 1, true, true)) { Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(player->getStepDuration(), boost::bind(&Game::playerMoveItem, this, playerId, fromPos, spriteId, fromStackpos, toPos, count)); player->setNextWalkActionTask(task); return true; } player->sendCancelMessage(RET_THEREISNOWAY); return false; } //hangable item specific code if(item->isHangable() && toCylinder->getTile()->hasProperty(SUPPORTHANGABLE)) { //destination supports hangable objects so need to move there first if(toCylinder->getTile()->hasProperty(ISVERTICAL)) { if(player->getPosition().x + 1 == mapToPos.x) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } } else if(toCylinder->getTile()->hasProperty(ISHORIZONTAL)) { if(player->getPosition().y + 1 == mapToPos.y) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } } if(!Position::areInRange<1,1,0>(playerPos, mapToPos) && !player->hasCustomFlag(PlayerCustomFlag_CanMoveFromFar)) { Position walkPos = mapToPos; if(toCylinder->getTile()->hasProperty(ISVERTICAL)) walkPos.x -= -1; if(toCylinder->getTile()->hasProperty(ISHORIZONTAL)) walkPos.y -= -1; Position itemPos = fromPos; int16_t itemStackpos = fromStackpos; if(fromPos.x != 0xFFFF && Position::areInRange<1,1,0>(mapFromPos, player->getPosition()) && !Position::areInRange<1,1,0>(mapFromPos, walkPos)) { //need to pickup the item first Item* moveItem = NULL; ReturnValue ret = internalMoveItem(player, fromCylinder, player, INDEX_WHEREEVER, item, count, &moveItem); if(ret != RET_NOERROR) { player->sendCancelMessage(ret); return false; } //changing the position since its now in the inventory of the player internalGetPosition(moveItem, itemPos, itemStackpos); } std::list listDir; if(map->getPathTo(player, walkPos, listDir)) { Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(player->getStepDuration(), boost::bind(&Game::playerMoveItem, this, playerId, itemPos, spriteId, itemStackpos, toPos, count)); player->setNextWalkActionTask(task); return true; } player->sendCancelMessage(RET_THEREISNOWAY); return false; } } if(!player->hasCustomFlag(PlayerCustomFlag_CanThrowAnywhere)) { if((std::abs(playerPos.x - mapToPos.x) > item->getThrowRange()) || (std::abs(playerPos.y - mapToPos.y) > item->getThrowRange()) || (std::abs(mapFromPos.z - mapToPos.z) * 4 > item->getThrowRange())) { player->sendCancelMessage(RET_DESTINATIONOUTOFREACH); return false; } } if(!canThrowObjectTo(mapFromPos, mapToPos) && !player->hasCustomFlag(PlayerCustomFlag_CanThrowAnywhere)) { player->sendCancelMessage(RET_CANNOTTHROW); return false; } ReturnValue ret = internalMoveItem(player, fromCylinder, toCylinder, toIndex, item, count, NULL); if(ret == RET_NOERROR) return true; player->sendCancelMessage(ret); return false; } ReturnValue Game::internalMoveItem(Creature* actor, Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/) { if(!toCylinder) return RET_NOTPOSSIBLE; if(!item->getParent()) { assert(fromCylinder == item->getParent()); return internalAddItem(actor, toCylinder, item, INDEX_WHEREEVER, FLAG_NOLIMIT); } Item* toItem = NULL; Cylinder* subCylinder = NULL; int32_t floor = 0; while((subCylinder = toCylinder->__queryDestination(index, item, &toItem, flags)) != toCylinder) { toCylinder = subCylinder; flags = 0; //to prevent infinite loop if(++floor >= MAP_MAX_LAYERS) break; } //destination is the same as the source? if(item == toItem) return RET_NOERROR; //silently ignore move //check if we can add this item ReturnValue ret = toCylinder->__queryAdd(index, item, count, flags); if(ret == RET_NEEDEXCHANGE) { //check if we can add it to source cylinder int32_t fromIndex = fromCylinder->__getIndexOfThing(item); ret = fromCylinder->__queryAdd(fromIndex, toItem, toItem->getItemCount(), 0); if(ret == RET_NOERROR) { //check how much we can move uint32_t maxExchangeQueryCount = 0; ReturnValue retExchangeMaxCount = fromCylinder->__queryMaxCount(-1, toItem, toItem->getItemCount(), maxExchangeQueryCount, 0); if(retExchangeMaxCount != RET_NOERROR && maxExchangeQueryCount == 0) return retExchangeMaxCount; if((toCylinder->__queryRemove(toItem, toItem->getItemCount(), flags) == RET_NOERROR) && ret == RET_NOERROR) { int32_t oldToItemIndex = toCylinder->__getIndexOfThing(toItem); toCylinder->__removeThing(toItem, toItem->getItemCount()); fromCylinder->__addThing(actor, toItem); if(oldToItemIndex != -1) toCylinder->postRemoveNotification(actor, toItem, fromCylinder, oldToItemIndex, true); int32_t newToItemIndex = fromCylinder->__getIndexOfThing(toItem); if(newToItemIndex != -1) fromCylinder->postAddNotification(actor, toItem, toCylinder, newToItemIndex); ret = toCylinder->__queryAdd(index, item, count, flags); toItem = NULL; } } } if(ret != RET_NOERROR) return ret; //check how much we can move uint32_t maxQueryCount = 0; ReturnValue retMaxCount = toCylinder->__queryMaxCount(index, item, count, maxQueryCount, flags); if(retMaxCount != RET_NOERROR && !maxQueryCount) return retMaxCount; uint32_t m = maxQueryCount, n = 0; if(item->isStackable()) m = std::min((uint32_t)count, m); Item* moveItem = item; //check if we can remove this item ret = fromCylinder->__queryRemove(item, m, flags); if(ret != RET_NOERROR) return ret; //remove the item int32_t itemIndex = fromCylinder->__getIndexOfThing(item); fromCylinder->__removeThing(item, m); bool isCompleteRemoval = item->isRemoved(); Item* updateItem = NULL; //update item(s) if(item->isStackable()) { if(toItem && toItem->getID() == item->getID()) { n = std::min((uint32_t)100 - toItem->getItemCount(), m); toCylinder->__updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); updateItem = toItem; } if(m - n > 0) moveItem = Item::CreateItem(item->getID(), m - n); else moveItem = NULL; if(item->isRemoved()) freeThing(item); } //add item if(moveItem /*m - n > 0*/) toCylinder->__addThing(actor, index, moveItem); if(itemIndex != -1) fromCylinder->postRemoveNotification(actor, item, toCylinder, itemIndex, isCompleteRemoval); if(moveItem) { int32_t moveItemIndex = toCylinder->__getIndexOfThing(moveItem); if(moveItemIndex != -1) toCylinder->postAddNotification(actor, moveItem, fromCylinder, moveItemIndex); } if(updateItem) { int32_t updateItemIndex = toCylinder->__getIndexOfThing(updateItem); if(updateItemIndex != -1) toCylinder->postAddNotification(actor, updateItem, fromCylinder, updateItemIndex); } if(_moveItem) { if(moveItem) *_moveItem = moveItem; else *_moveItem = item; } //we could not move all, inform the player if(item->isStackable() && maxQueryCount < count) return retMaxCount; return ret; } ReturnValue Game::internalAddItem(Creature* actor, Cylinder* toCylinder, Item* item, int32_t index /*= INDEX_WHEREEVER*/, uint32_t flags /*= 0*/, bool test /*= false*/) { if(!toCylinder || !item) return RET_NOTPOSSIBLE; Item* toItem = NULL; toCylinder = toCylinder->__queryDestination(index, item, &toItem, flags); ReturnValue ret = toCylinder->__queryAdd(index, item, item->getItemCount(), flags); if(ret != RET_NOERROR) return ret; uint32_t maxQueryCount = 0; ret = toCylinder->__queryMaxCount(index, item, item->getItemCount(), maxQueryCount, flags); if(ret != RET_NOERROR) return ret; if(!test) { uint32_t m = maxQueryCount; if(item->isStackable()) m = std::min((uint32_t)item->getItemCount(), maxQueryCount); Item* moveItem = item; if(item->isStackable() && toItem && toItem->getID() == item->getID()) { uint32_t n = std::min((uint32_t)100 - toItem->getItemCount(), m); toCylinder->__updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); if(m - n > 0) { if(m - n != item->getItemCount()) moveItem = Item::CreateItem(item->getID(), m - n); } else { moveItem = NULL; if(item->getParent() != VirtualCylinder::virtualCylinder) { item->onRemoved(); freeThing(item); } } } if(moveItem) { toCylinder->__addThing(actor, index, moveItem); int32_t moveItemIndex = toCylinder->__getIndexOfThing(moveItem); if(moveItemIndex != -1) toCylinder->postAddNotification(actor, moveItem, NULL, moveItemIndex); } else { int32_t itemIndex = toCylinder->__getIndexOfThing(item); if(itemIndex != -1) toCylinder->postAddNotification(actor, item, NULL, itemIndex); } } return RET_NOERROR; } ReturnValue Game::internalRemoveItem(Creature* actor, Item* item, int32_t count /*= -1*/, bool test /*= false*/, uint32_t flags /*= 0*/) { Cylinder* cylinder = item->getParent(); if(!cylinder) return RET_NOTPOSSIBLE; if(count == -1) count = item->getItemCount(); //check if we can remove this item ReturnValue ret = cylinder->__queryRemove(item, count, flags | FLAG_IGNORENOTMOVEABLE); if(ret != RET_NOERROR) return ret; if(!item->canRemove()) return RET_NOTPOSSIBLE; if(!test) { //remove the item int32_t index = cylinder->__getIndexOfThing(item); cylinder->__removeThing(item, count); bool isCompleteRemoval = false; if(item->isRemoved()) { isCompleteRemoval = true; freeThing(item); } cylinder->postRemoveNotification(actor, item, NULL, index, isCompleteRemoval); } item->onRemoved(); return RET_NOERROR; } ReturnValue Game::internalPlayerAddItem(Creature* actor, Player* player, Item* item, bool dropOnMap /*= true*/) { ReturnValue ret = internalAddItem(actor, player, item); if(ret != RET_NOERROR && dropOnMap) ret = internalAddItem(actor, player->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT); return ret; } Item* Game::findItemOfType(Cylinder* cylinder, uint16_t itemId, bool depthSearch /*= true*/, int32_t subType /*= -1*/) { if(!cylinder) return NULL; std::list listContainer; Container* tmpContainer = NULL; Thing* thing = NULL; Item* item = NULL; for(int32_t i = cylinder->__getFirstIndex(); i < cylinder->__getLastIndex();) { if((thing = cylinder->__getThing(i)) && (item = thing->getItem())) { if(item->getID() == itemId && (subType == -1 || subType == item->getSubType())) return item; else { ++i; if(depthSearch && (tmpContainer = item->getContainer())) listContainer.push_back(tmpContainer); } } else ++i; } while(listContainer.size() > 0) { Container* container = listContainer.front(); listContainer.pop_front(); for(int32_t i = 0; i < (int32_t)container->size();) { Item* item = container->getItem(i); if(item->getID() == itemId && (subType == -1 || subType == item->getSubType())) return item; else { ++i; if((tmpContainer = item->getContainer())) listContainer.push_back(tmpContainer); } } } return NULL; } bool Game::removeItemOfType(Cylinder* cylinder, uint16_t itemId, int32_t count, int32_t subType /*= -1*/) { if(!cylinder || ((int32_t)cylinder->__getItemTypeCount(itemId, subType) < count)) return false; if(count <= 0) return true; std::list listContainer; Container* tmpContainer = NULL; Thing* thing = NULL; Item* item = NULL; for(int32_t i = cylinder->__getFirstIndex(); i < cylinder->__getLastIndex() && count > 0;) { if((thing = cylinder->__getThing(i)) && (item = thing->getItem())) { if(item->getID() == itemId) { if(item->isStackable()) { if(item->getItemCount() > count) { internalRemoveItem(NULL, item, count); count = 0; } else { count -= item->getItemCount(); internalRemoveItem(NULL, item); } } else if(subType == -1 || subType == item->getSubType()) { --count; internalRemoveItem(NULL, item); } } else { ++i; if((tmpContainer = item->getContainer())) listContainer.push_back(tmpContainer); } } else ++i; } while(listContainer.size() > 0 && count > 0) { Container* container = listContainer.front(); listContainer.pop_front(); for(int32_t i = 0; i < (int32_t)container->size() && count > 0;) { Item* item = container->getItem(i); if(item->getID() == itemId) { if(item->isStackable()) { if(item->getItemCount() > count) { internalRemoveItem(NULL, item, count); count = 0; } else { count-= item->getItemCount(); internalRemoveItem(NULL, item); } } else if(subType == -1 || subType == item->getSubType()) { --count; internalRemoveItem(NULL, item); } } else { ++i; if((tmpContainer = item->getContainer())) listContainer.push_back(tmpContainer); } } } return (count == 0); } uint32_t Game::getMoney(const Cylinder* cylinder) { if(!cylinder) return 0; std::list listContainer; Container* tmpContainer = NULL; Thing* thing = NULL; Item* item = NULL; uint32_t moneyCount = 0; for(int32_t i = cylinder->__getFirstIndex(); i < cylinder->__getLastIndex(); ++i) { if(!(thing = cylinder->__getThing(i))) continue; if(!(item = thing->getItem())) continue; if((tmpContainer = item->getContainer())) listContainer.push_back(tmpContainer); else if(item->getWorth() != 0) moneyCount += item->getWorth(); } while(listContainer.size() > 0) { Container* container = listContainer.front(); listContainer.pop_front(); for(ItemList::const_iterator it = container->getItems(); it != container->getEnd(); ++it) { item = *it; if((tmpContainer = item->getContainer())) listContainer.push_back(tmpContainer); else if(item->getWorth() != 0) moneyCount += item->getWorth(); } } return moneyCount; } bool Game::removeMoney(Cylinder* cylinder, int32_t money, uint32_t flags /*= 0*/) { if(!cylinder) return false; if(money <= 0) return true; typedef std::multimap > MoneyMultiMap; MoneyMultiMap moneyMap; std::list listContainer; Container* tmpContainer = NULL; Thing* thing = NULL; Item* item = NULL; int32_t moneyCount = 0; for(int32_t i = cylinder->__getFirstIndex(); i < cylinder->__getLastIndex() && money > 0; ++i) { if(!(thing = cylinder->__getThing(i))) continue; if(!(item = thing->getItem())) continue; if((tmpContainer = item->getContainer())) listContainer.push_back(tmpContainer); else if(item->getWorth() != 0) { moneyCount += item->getWorth(); moneyMap.insert(std::make_pair(item->getWorth(), item)); } } while(listContainer.size() > 0 && money > 0) { Container* container = listContainer.front(); listContainer.pop_front(); for(int32_t i = 0; i < (int32_t)container->size() && money > 0; i++) { Item* item = container->getItem(i); if((tmpContainer = item->getContainer())) listContainer.push_back(tmpContainer); else if(item->getWorth() != 0) { moneyCount += item->getWorth(); moneyMap.insert(std::make_pair(item->getWorth(), item)); } } } // Not enough money if(moneyCount < money) return false; for(MoneyMultiMap::iterator mit = moneyMap.begin(); mit != moneyMap.end() && money > 0; ++mit) { Item* item = mit->second; if(!item) continue; internalRemoveItem(NULL, item); if(mit->first > money) { // Remove a monetary value from an item int32_t remaind = item->getWorth() - money; addMoney(cylinder, remaind, flags); money = 0; } else money -= mit->first; mit->second = NULL; } moneyMap.clear(); return money == 0; } void Game::addMoney(Cylinder* cylinder, int32_t money, uint32_t flags /*= 0*/) { IntegerMap moneyMap = Item::items.getMoneyMap(); int32_t tmp = 0; for(IntegerMap::reverse_iterator it = moneyMap.rbegin(); it != moneyMap.rend(); ++it) { tmp = money / it->first; money -= tmp * it->first; if(tmp != 0) { do { Item* remaindItem = Item::CreateItem(it->second, std::min(100, tmp)); if(internalAddItem(NULL, cylinder, remaindItem, INDEX_WHEREEVER, flags) != RET_NOERROR) internalAddItem(NULL, cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); tmp -= std::min(100, tmp); } while(tmp > 0); } } } Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) { if(item->getID() == newId && (newCount == -1 || (newCount == item->getSubType() && newCount != 0))) return item; Cylinder* cylinder = item->getParent(); if(!cylinder) return NULL; int32_t itemIndex = cylinder->__getIndexOfThing(item); if(itemIndex == -1) { #ifdef __DEBUG__ std::cout << "Error: transformItem, itemIndex == -1" << std::endl; #endif return item; } if(!item->canTransform()) return item; const ItemType& curType = Item::items[item->getID()]; const ItemType& newType = Item::items[newId]; 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 ReturnValue ret = internalRemoveItem(NULL, item); if(ret != RET_NOERROR) return item; Item* newItem = NULL; if(newCount == -1) newItem = Item::CreateItem(newId); else newItem = Item::CreateItem(newId, newCount); if(!newItem) return NULL; newItem->copyAttributes(item); if(internalAddItem(NULL, cylinder, newItem, INDEX_WHEREEVER, FLAG_NOLIMIT) == RET_NOERROR) return newItem; delete newItem; return NULL; } if(curType.type == newType.type) { //Both items has the same type so we can safely change id/subtype if(!newCount && (item->isStackable() || item->hasCharges())) { if(!item->isStackable() && (!item->getDefaultDuration() || item->getDuration() <= 0)) { int32_t tmpId = newId; if(curType.id == newType.id) tmpId = curType.decayTo; if(tmpId != -1) { item = transformItem(item, tmpId); return item; } } internalRemoveItem(NULL, item); return NULL; } uint16_t itemId = item->getID(); int32_t count = item->getSubType(); cylinder->postRemoveNotification(NULL, item, cylinder, itemIndex, false); if(curType.id != newType.id) { itemId = newId; if(newType.group != curType.group) item->setDefaultSubtype(); } if(newCount != -1 && newType.hasSubType()) count = newCount; cylinder->__updateThing(item, itemId, count); cylinder->postAddNotification(NULL, item, cylinder, itemIndex); return item; } //Replacing the the old item with the new while maintaining the old position Item* newItem = NULL; if(newCount == -1) newItem = Item::CreateItem(newId); else newItem = Item::CreateItem(newId, newCount); if(!newItem) { #ifdef __DEBUG__ std::cout << "Error: [Game::transformItem] Item of type " << item->getID() << " transforming into invalid type " << newId << std::endl; #endif return NULL; } cylinder->__replaceThing(itemIndex, newItem); cylinder->postAddNotification(NULL, newItem, cylinder, itemIndex); item->setParent(NULL); cylinder->postRemoveNotification(NULL, item, cylinder, itemIndex, true); freeThing(item); return newItem; } ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pushMove, uint32_t flags /*= 0*/) { if(newPos == thing->getPosition()) return RET_NOERROR; if(thing->isRemoved()) return RET_NOTPOSSIBLE; if(Tile* toTile = map->getTile(newPos)) { if(Creature* creature = thing->getCreature()) { if(Position::areInRange<1,1,0>(creature->getPosition(), newPos) && pushMove) creature->getTile()->moveCreature(NULL, creature, toTile, false); else creature->getTile()->moveCreature(NULL, creature, toTile, true); return RET_NOERROR; } if(Item* item = thing->getItem()) return internalMoveItem(NULL, item->getParent(), toTile, INDEX_WHEREEVER, item, item->getItemCount(), NULL, flags); } return RET_NOTPOSSIBLE; } //Implementation of player invoked events bool Game::playerMove(uint32_t playerId, Direction dir) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(player->getNoMove()) { player->sendCancelWalk(); return false; } player->stopWalk(); int32_t delay = player->getWalkDelay(dir); if(delay > 0) { player->setNextAction(OTSYS_TIME() + player->getStepDuration(dir) - SCHEDULER_MINTICKS); if(SchedulerTask* task = createSchedulerTask(((uint32_t)delay), boost::bind(&Game::playerMove, this, playerId, dir))) player->setNextWalkTask(task); return false; } player->setFollowCreature(NULL); player->onWalk(dir); player->setIdleTime(0); return internalMoveCreature(player, dir) == RET_NOERROR; } bool Game::playerBroadcastMessage(Player* player, SpeakClasses type, const std::string& text) { if(!player->hasFlag(PlayerFlag_CanBroadcast) || type < SPEAK_CLASS_FIRST || type > SPEAK_CLASS_LAST) return false; for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) it->second->sendCreatureSay(player, type, text); //TODO: event handling - onCreatureSay std::cout << "> " << player->getName() << " broadcasted: \"" << text << "\"." << std::endl; return true; } bool Game::playerCreatePrivateChannel(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved() || !player->isPremium()) return false; ChatChannel* channel = g_chat.createChannel(player, 0xFFFF); if(!channel || !channel->addUser(player)) return false; player->sendCreatePrivateChannel(channel->getId(), channel->getName()); return true; } bool Game::playerChannelInvite(uint32_t playerId, const std::string& name) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; PrivateChatChannel* channel = g_chat.getPrivateChannel(player); if(!channel) return false; Player* invitePlayer = getPlayerByName(name); if(!invitePlayer) return false; channel->invitePlayer(player, invitePlayer); return true; } bool Game::playerChannelExclude(uint32_t playerId, const std::string& name) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; PrivateChatChannel* channel = g_chat.getPrivateChannel(player); if(!channel) return false; Player* excludePlayer = getPlayerByName(name); if(!excludePlayer) return false; channel->excludePlayer(player, excludePlayer); return true; } bool Game::playerRequestChannels(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; player->sendChannelsDialog(); return true; } bool Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; ChatChannel* channel = g_chat.addUserToChannel(player, channelId); if(!channel) { #ifdef __DEBUG_CHAT__ std::cout << "Game::playerOpenChannel - failed adding user to channel." << std::endl; #endif return false; } if(channel->getId() != CHANNEL_RVR) player->sendChannel(channel->getId(), channel->getName()); else player->sendRuleViolationsChannel(channel->getId()); return true; } bool Game::playerCloseChannel(uint32_t playerId, uint16_t channelId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; g_chat.removeUserFromChannel(player, channelId); return true; } bool Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(IOLoginData::getInstance()->playerExists(receiver)) player->sendOpenPrivateChannel(receiver); else player->sendCancel("A player with this name does not exist."); return true; } bool Game::playerProcessRuleViolation(uint32_t playerId, const std::string& name) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(!player->hasFlag(PlayerFlag_CanAnswerRuleViolations)) return false; Player* reporter = getPlayerByName(name); if(!reporter) return false; RuleViolationsMap::iterator it = ruleViolations.find(reporter->getID()); if(it == ruleViolations.end()) return false; RuleViolation& rvr = *it->second; if(!rvr.isOpen) return false; rvr.isOpen = false; rvr.gamemaster = player; if(ChatChannel* channel = g_chat.getChannelById(CHANNEL_RVR)) { UsersMap tmpMap = channel->getUsers(); for(UsersMap::iterator tit = tmpMap.begin(); tit != tmpMap.end(); ++tit) tit->second->sendRemoveReport(reporter->getName()); } return true; } bool Game::playerCloseRuleViolation(uint32_t playerId, const std::string& name) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Player* reporter = getPlayerByName(name); if(!reporter) return false; return closeRuleViolation(reporter); } bool Game::playerCancelRuleViolation(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; return cancelRuleViolation(player); } bool Game::playerCloseNpcChannel(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; SpectatorVec list; SpectatorVec::iterator it; getSpectators(list, player->getPosition()); Npc* npc = NULL; for(it = list.begin(); it != list.end(); ++it) { if((npc = (*it)->getNpc())) npc->onPlayerCloseChannel(player); } return true; } bool Game::playerReceivePing(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; player->receivePing(); return true; } bool Game::playerAutoWalk(uint32_t playerId, std::list& listDir) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; player->setIdleTime(0); player->setNextWalkTask(NULL); return player->startAutoWalk(listDir); } bool Game::playerStopAutoWalk(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; player->stopWalk(); return true; } bool Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, int16_t fromStackpos, uint16_t fromSpriteId, const Position& toPos, int16_t toStackpos, uint16_t toSpriteId, bool isHotkey) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(isHotkey && !g_config.getBool(ConfigManager::AIMBOT_HOTKEY_ENABLED)) return false; Thing* thing = internalGetThing(player, fromPos, fromStackpos, fromSpriteId, STACKPOS_USEITEM); if(!thing) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } Item* item = thing->getItem(); if(!item || !item->isUseable()) { player->sendCancelMessage(RET_CANNOTUSETHISOBJECT); return false; } Position walkToPos = fromPos; ReturnValue ret = g_actions->canUse(player, fromPos); if(ret == RET_NOERROR) { ret = g_actions->canUse(player, toPos, item); if(ret == RET_TOOFARAWAY) walkToPos = toPos; } if(ret != RET_NOERROR) { if(ret == RET_TOOFARAWAY) { Position itemPos = fromPos; int16_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 = NULL; ReturnValue retTmp = internalMoveItem(player, item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem); if(retTmp != RET_NOERROR) { player->sendCancelMessage(retTmp); return false; } //changing the position since its now in the inventory of the player internalGetPosition(moveItem, itemPos, itemStackpos); } std::list listDir; if(getPathToEx(player, walkToPos, listDir, 0, 1, true, true, 10)) { Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerUseItemEx, this, playerId, itemPos, itemStackpos, fromSpriteId, toPos, toStackpos, toSpriteId, isHotkey)); player->setNextWalkActionTask(task); return true; } ret = RET_THEREISNOWAY; } player->sendCancelMessage(ret); return false; } if(!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerUseItemEx, this, playerId, fromPos, fromStackpos, fromSpriteId, toPos, toStackpos, toSpriteId, isHotkey)); player->setNextActionTask(task); return false; } if(isHotkey) showHotkeyUseMessage(player, item); player->setIdleTime(0); player->setNextActionTask(NULL); return g_actions->useItemEx(player, fromPos, toPos, toStackpos, item, isHotkey); } bool Game::playerUseItem(uint32_t playerId, const Position& pos, int16_t stackpos, uint8_t index, uint16_t spriteId, bool isHotkey) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(isHotkey && !g_config.getBool(ConfigManager::AIMBOT_HOTKEY_ENABLED)) return false; Thing* thing = internalGetThing(player, pos, stackpos, spriteId, STACKPOS_USEITEM); if(!thing) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } Item* item = thing->getItem(); if(!item || item->isUseable()) { player->sendCancelMessage(RET_CANNOTUSETHISOBJECT); return false; } ReturnValue ret = g_actions->canUse(player, pos); if(ret != RET_NOERROR) { if(ret == RET_TOOFARAWAY) { std::list listDir; if(getPathToEx(player, pos, listDir, 0, 1, true, true)) { Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerUseItem, this, playerId, pos, stackpos, index, spriteId, isHotkey)); player->setNextWalkActionTask(task); return true; } ret = RET_THEREISNOWAY; } player->sendCancelMessage(ret); return false; } if(!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerUseItem, this, playerId, pos, stackpos, index, spriteId, isHotkey)); player->setNextActionTask(task); return false; } if(isHotkey) showHotkeyUseMessage(player, item); player->setIdleTime(0); player->setNextActionTask(NULL); return g_actions->useItem(player, pos, index, item); } bool Game::playerUseBattleWindow(uint32_t playerId, const Position& fromPos, int16_t fromStackpos, uint32_t creatureId, uint16_t spriteId, bool isHotkey) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Creature* creature = getCreatureByID(creatureId); if(!creature) return false; if(!Position::areInRange<7,5,1>(creature->getPosition(), player->getPosition())) return false; if(!g_config.getBool(ConfigManager::AIMBOT_HOTKEY_ENABLED) && (creature->getPlayer() || isHotkey)) { player->sendCancelMessage(RET_DIRECTPLAYERSHOOT); return false; } Thing* thing = internalGetThing(player, fromPos, fromStackpos, spriteId, STACKPOS_USE); if(!thing) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } Item* item = thing->getItem(); if(!item || item->getClientID() != spriteId) { player->sendCancelMessage(RET_CANNOTUSETHISOBJECT); return false; } ReturnValue ret = g_actions->canUse(player, fromPos); if(ret != RET_NOERROR) { if(ret == RET_TOOFARAWAY) { std::list listDir; if(getPathToEx(player, item->getPosition(), listDir, 0, 1, true, true)) { Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerUseBattleWindow, this, playerId, fromPos, fromStackpos, creatureId, spriteId, isHotkey)); player->setNextWalkActionTask(task); return true; } ret = RET_THEREISNOWAY; } player->sendCancelMessage(ret); return false; } if(!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerUseBattleWindow, this, playerId, fromPos, fromStackpos, creatureId, spriteId, isHotkey)); player->setNextActionTask(task); return false; } if(isHotkey) showHotkeyUseMessage(player, item); player->setIdleTime(0); player->setNextActionTask(NULL); return g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->__getIndexOfThing(creature), item, isHotkey, creatureId); } bool Game::playerCloseContainer(uint32_t playerId, uint8_t cid) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; player->closeContainer(cid); player->sendCloseContainer(cid); return true; } bool Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Container* container = player->getContainer(cid); if(!container) return false; Container* parentContainer = dynamic_cast(container->getParent()); if(!parentContainer) return false; player->addContainer(cid, parentContainer); player->sendContainer(cid, parentContainer, (dynamic_cast(parentContainer->getParent()) != NULL)); return true; } bool Game::playerUpdateTile(uint32_t playerId, const Position& pos) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(!player->canSee(pos)) return false; if(Tile* tile = getTile(pos)) player->sendUpdateTile(tile, pos); return true; } bool Game::playerUpdateContainer(uint32_t playerId, uint8_t cid) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Container* container = player->getContainer(cid); if(!container) return false; player->sendContainer(cid, container, (dynamic_cast(container->getParent()) != NULL)); return true; } bool Game::playerRotateItem(uint32_t playerId, const Position& pos, int16_t stackpos, const uint16_t spriteId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Thing* thing = internalGetThing(player, pos, stackpos); if(!thing) return false; Item* item = thing->getItem(); if(!item || item->getClientID() != spriteId || !item->isRoteable() || (item->isLoadedFromMap() && (item->getUniqueId() != 0 || (item->getActionId() != 0 && item->getContainer())))) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } if(pos.x != 0xFFFF && !Position::areInRange<1,1,0>(pos, player->getPosition())) { std::list listDir; if(getPathToEx(player, pos, listDir, 0, 1, true, true)) { Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerRotateItem, this, playerId, pos, stackpos, spriteId)); player->setNextWalkActionTask(task); return true; } player->sendCancelMessage(RET_THEREISNOWAY); return false; } uint16_t newId = Item::items[item->getID()].rotateTo; if(newId != 0) transformItem(item, newId); player->setIdleTime(0); return true; } bool Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; uint16_t maxTextLength = 0; uint32_t internalWindowTextId = 0; Item* writeItem = player->getWriteItem(internalWindowTextId, maxTextLength); if(text.length() > maxTextLength || windowTextId != internalWindowTextId) return false; if(!writeItem || writeItem->isRemoved()) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } Cylinder* topParent = writeItem->getTopParent(); Player* owner = dynamic_cast(topParent); if(owner && owner != player) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } if(!Position::areInRange<1,1,0>(writeItem->getPosition(), player->getPosition())) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } bool deny = false; CreatureEventList textEditEvents = player->getCreatureEvents(CREATURE_EVENT_TEXTEDIT); for(CreatureEventList::iterator it = textEditEvents.begin(); it != textEditEvents.end(); ++it) { if(!(*it)->executeTextEdit(player, writeItem, text)) deny = true; } if(deny) return false; if(!text.empty()) { if(writeItem->getText() != text) { writeItem->setText(text); writeItem->setWriter(player->getName()); writeItem->setDate(std::time(NULL)); } } else { writeItem->resetText(); writeItem->resetWriter(); writeItem->resetDate(); } uint16_t newId = Item::items[writeItem->getID()].writeOnceItemId; if(newId != 0) transformItem(writeItem, newId); player->setWriteItem(NULL); return true; } bool Game::playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; uint32_t internalWindowTextId = 0; uint32_t internalListId = 0; House* house = player->getEditHouse(internalWindowTextId, internalListId); if(house && internalWindowTextId == windowTextId && listId == 0) { house->setAccessList(internalListId, text); player->setEditHouse(NULL); } return true; } bool Game::playerRequestTrade(uint32_t playerId, const Position& pos, int16_t stackpos, uint32_t tradePlayerId, uint16_t spriteId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Player* tradePartner = getPlayerByID(tradePlayerId); if(!tradePartner || tradePartner == player) { player->sendTextMessage(MSG_INFO_DESCR, "Sorry, not possible."); return false; } if(!Position::areInRange<2,2,0>(tradePartner->getPosition(), player->getPosition())) { std::stringstream ss; ss << tradePartner->getName() << " tells you to move closer."; player->sendTextMessage(MSG_INFO_DESCR, ss.str()); return false; } Item* tradeItem = dynamic_cast(internalGetThing(player, pos, stackpos, spriteId, STACKPOS_USE)); if(!tradeItem || tradeItem->getClientID() != spriteId || !tradeItem->isPickupable() || (tradeItem->isLoadedFromMap() && (tradeItem->getUniqueId() != 0 || (tradeItem->getActionId() != 0 && tradeItem->getContainer())))) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } if(player->getPosition().z > tradeItem->getPosition().z) { player->sendCancelMessage(RET_FIRSTGOUPSTAIRS); return false; } if(player->getPosition().z < tradeItem->getPosition().z) { player->sendCancelMessage(RET_FIRSTGODOWNSTAIRS); return false; } if(!Position::areInRange<1,1,0>(tradeItem->getPosition(), player->getPosition())) { std::list listDir; if(getPathToEx(player, pos, listDir, 0, 1, true, true)) { Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerRequestTrade, this, playerId, pos, stackpos, tradePlayerId, spriteId)); player->setNextWalkActionTask(task); return true; } player->sendCancelMessage(RET_THEREISNOWAY); return false; } const Container* container = NULL; for(std::map::const_iterator it = tradeItems.begin(); it != tradeItems.end(); it++) { if(tradeItem == it->first || ((container = dynamic_cast(tradeItem)) && container->isHoldingItem(it->first)) || ((container = dynamic_cast(it->first)) && container->isHoldingItem(tradeItem))) { player->sendTextMessage(MSG_INFO_DESCR, "This item is already being traded."); return false; } } Container* tradeContainer = tradeItem->getContainer(); if(tradeContainer && tradeContainer->getItemHoldingCount() + 1 > 100) { player->sendTextMessage(MSG_INFO_DESCR, "You cannot trade more than 100 items."); return false; } bool deny = false; CreatureEventList tradeEvents = player->getCreatureEvents(CREATURE_EVENT_TRADE_REQUEST); for(CreatureEventList::iterator it = tradeEvents.begin(); it != tradeEvents.end(); ++it) { if(!(*it)->executeTradeRequest(player, tradePartner, tradeItem)) deny = true; } if(deny) return false; 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(RET_YOUAREALREADYTRADING); return false; } else if(tradePartner->tradeState != TRADE_NONE && tradePartner->tradePartner != player) { player->sendCancelMessage(RET_THISPLAYERISALREADYTRADING); return false; } player->tradePartner = tradePartner; player->tradeItem = tradeItem; player->tradeState = TRADE_INITIATED; tradeItem->addRef(); tradeItems[tradeItem] = player->getID(); player->sendTradeItemRequest(player, tradeItem, true); if(tradePartner->tradeState == TRADE_NONE) { char buffer[100]; sprintf(buffer, "%s wants to trade with you", player->getName().c_str()); tradePartner->sendTextMessage(MSG_INFO_DESCR, buffer); tradePartner->tradeState = TRADE_ACKNOWLEDGE; tradePartner->tradePartner = player; } else { Item* counterOfferItem = tradePartner->tradeItem; player->sendTradeItemRequest(tradePartner, counterOfferItem, false); tradePartner->sendTradeItemRequest(player, tradeItem, false); } return true; } bool Game::playerAcceptTrade(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(!(player->getTradeState() == TRADE_ACKNOWLEDGE || player->getTradeState() == TRADE_INITIATED)) return false; player->setTradeState(TRADE_ACCEPT); Player* tradePartner = player->tradePartner; if(!tradePartner || tradePartner->getTradeState() != TRADE_ACCEPT) return false; Item* tradeItem1 = player->tradeItem; Item* tradeItem2 = tradePartner->tradeItem; bool deny = false; CreatureEventList tradeEvents = player->getCreatureEvents(CREATURE_EVENT_TRADE_ACCEPT); for(CreatureEventList::iterator it = tradeEvents.begin(); it != tradeEvents.end(); ++it) { if(!(*it)->executeTradeAccept(player, tradePartner, tradeItem1, tradeItem2)) deny = true; } if(deny) return false; player->setTradeState(TRADE_TRANSFER); tradePartner->setTradeState(TRADE_TRANSFER); std::map::iterator it = tradeItems.find(tradeItem1); if(it != tradeItems.end()) { freeThing(it->first); tradeItems.erase(it); } it = tradeItems.find(tradeItem2); if(it != tradeItems.end()) { freeThing(it->first); tradeItems.erase(it); } ReturnValue ret1 = internalAddItem(player, tradePartner, tradeItem1, INDEX_WHEREEVER, 0, true); ReturnValue ret2 = internalAddItem(tradePartner, player, tradeItem2, INDEX_WHEREEVER, 0, true); bool isSuccess = false; if(ret1 == RET_NOERROR && ret2 == RET_NOERROR) { ret1 = internalRemoveItem(tradePartner, tradeItem1, tradeItem1->getItemCount(), true); ret2 = internalRemoveItem(player, tradeItem2, tradeItem2->getItemCount(), true); if(ret1 == RET_NOERROR && ret2 == RET_NOERROR) { Cylinder* cylinder1 = tradeItem1->getParent(); Cylinder* cylinder2 = tradeItem2->getParent(); internalMoveItem(player, cylinder1, tradePartner, INDEX_WHEREEVER, tradeItem1, tradeItem1->getItemCount(), NULL); internalMoveItem(tradePartner, cylinder2, player, INDEX_WHEREEVER, tradeItem2, tradeItem2->getItemCount(), NULL); tradeItem1->onTradeEvent(ON_TRADE_TRANSFER, tradePartner, player); tradeItem2->onTradeEvent(ON_TRADE_TRANSFER, player, tradePartner); isSuccess = true; } } if(!isSuccess) { std::string errorDescription = getTradeErrorDescription(ret1, tradeItem1); tradePartner->sendTextMessage(MSG_INFO_DESCR, errorDescription); tradeItem2->onTradeEvent(ON_TRADE_CANCEL, tradePartner, NULL); errorDescription = getTradeErrorDescription(ret2, tradeItem2); player->sendTextMessage(MSG_INFO_DESCR, errorDescription); tradeItem1->onTradeEvent(ON_TRADE_CANCEL, player, NULL); } player->setTradeState(TRADE_NONE); player->tradeItem = NULL; player->tradePartner = NULL; player->sendTradeClose(); tradePartner->setTradeState(TRADE_NONE); tradePartner->tradeItem = NULL; tradePartner->tradePartner = NULL; tradePartner->sendTradeClose(); return isSuccess; } std::string Game::getTradeErrorDescription(ReturnValue ret, Item* item) { std::stringstream ss; if(ret == RET_NOTENOUGHCAPACITY) { ss << "You do not have enough capacity to carry"; if(item->isStackable() && item->getItemCount() > 1) ss << " these objects."; else ss << " this object." ; ss << std::endl << " " << item->getWeightDescription(); } else if(ret == RET_NOTENOUGHROOM || ret == RET_CONTAINERNOTENOUGHROOM) { ss << "You do not have enough room to carry"; if(item->isStackable() && item->getItemCount() > 1) ss << " these objects."; else ss << " this object."; } else ss << "Trade could not be completed."; return ss.str().c_str(); } bool Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, int32_t index) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Player* tradePartner = player->tradePartner; if(!tradePartner) return false; Item* tradeItem = NULL; if(lookAtCounterOffer) tradeItem = tradePartner->getTradeItem(); else tradeItem = player->getTradeItem(); if(!tradeItem) return false; std::stringstream ss; ss << "You see "; int32_t lookDistance = std::max(std::abs(player->getPosition().x - tradeItem->getPosition().x), std::abs(player->getPosition().y - tradeItem->getPosition().y)); if(!index) { ss << tradeItem->getDescription(lookDistance); if(player->hasCustomFlag(PlayerCustomFlag_CanSeeItemDetails)) { ss << std::endl << "ItemID: [" << tradeItem->getID() << "]"; if(tradeItem->getActionId() > 0) ss << ", ActionID: [" << tradeItem->getActionId() << "]"; if(tradeItem->getUniqueId() > 0) ss << ", UniqueID: [" << tradeItem->getUniqueId() << "]"; ss << "."; const ItemType& it = Item::items[tradeItem->getID()]; if(it.transformEquipTo) ss << std::endl << "TransformTo (onEquip): [" << it.transformEquipTo << "]"; else if(it.transformDeEquipTo) ss << std::endl << "TransformTo (onDeEquip): [" << it.transformDeEquipTo << "]"; else if(it.decayTo != -1) ss << std::endl << "DecayTo: [" << it.decayTo << "]"; } player->sendTextMessage(MSG_INFO_DESCR, ss.str()); return false; } Container* tradeContainer = tradeItem->getContainer(); if(!tradeContainer || index > (int32_t)tradeContainer->getItemHoldingCount()) return false; std::list listContainer; listContainer.push_back(tradeContainer); ItemList::const_iterator it; Container* tmpContainer = NULL; while(listContainer.size() > 0) { const Container* container = listContainer.front(); listContainer.pop_front(); for(it = container->getItems(); it != container->getEnd(); ++it) { if((tmpContainer = (*it)->getContainer())) listContainer.push_back(tmpContainer); --index; if(index != 0) continue; ss << (*it)->getDescription(lookDistance); if(player->hasCustomFlag(PlayerCustomFlag_CanSeeItemDetails)) { ss << std::endl << "ItemID: [" << (*it)->getID() << "]"; if((*it)->getActionId() > 0) ss << ", ActionID: [" << (*it)->getActionId() << "]"; if((*it)->getUniqueId() > 0) ss << ", UniqueID: [" << (*it)->getUniqueId() << "]"; ss << "."; const ItemType& iit = Item::items[(*it)->getID()]; if(iit.transformEquipTo) ss << std::endl << "TransformTo: [" << iit.transformEquipTo << "] (onEquip)."; else if(iit.transformDeEquipTo) ss << std::endl << "TransformTo: [" << iit.transformDeEquipTo << "] (onDeEquip)."; else if(iit.decayTo != -1) ss << std::endl << "DecayTo: [" << iit.decayTo << "]."; } player->sendTextMessage(MSG_INFO_DESCR, ss.str()); return true; } } return false; } bool Game::playerCloseTrade(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; return internalCloseTrade(player); } bool Game::internalCloseTrade(Player* player) { Player* tradePartner = player->tradePartner; if((tradePartner && tradePartner->getTradeState() == TRADE_TRANSFER) || player->getTradeState() == TRADE_TRANSFER) { std::cout << "[Warning - Game::internalCloseTrade] TradeState == TRADE_TRANSFER, " << player->getName() << " " << player->getTradeState() << ", " << tradePartner->getName() << " " << tradePartner->getTradeState() << std::endl; return true; } std::vector::iterator it; if(player->getTradeItem()) { std::map::iterator it = tradeItems.find(player->getTradeItem()); if(it != tradeItems.end()) { freeThing(it->first); tradeItems.erase(it); } player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player, NULL); player->tradeItem = NULL; } player->setTradeState(TRADE_NONE); player->tradePartner = NULL; player->sendTextMessage(MSG_STATUS_SMALL, "Trade cancelled."); player->sendTradeClose(); if(tradePartner) { if(tradePartner->getTradeItem()) { std::map::iterator it = tradeItems.find(tradePartner->getTradeItem()); if(it != tradeItems.end()) { freeThing(it->first); tradeItems.erase(it); } tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner, NULL); tradePartner->tradeItem = NULL; } tradePartner->setTradeState(TRADE_NONE); tradePartner->tradePartner = NULL; tradePartner->sendTextMessage(MSG_STATUS_SMALL, "Trade cancelled."); tradePartner->sendTradeClose(); } return true; } bool Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreCap/* = false*/, bool inBackpacks/* = false*/) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; int32_t onBuy, onSell; Npc* merchant = player->getShopOwner(onBuy, onSell); if(!merchant) return false; const ItemType& it = Item::items.getItemIdByClientId(spriteId); if(!it.id) return false; uint8_t subType = count; if(it.isFluidContainer() && count < uint8_t(sizeof(reverseFluidMap) / sizeof(int8_t))) subType = reverseFluidMap[count]; if(!player->canShopItem(it.id, subType, SHOPEVENT_BUY)) return false; merchant->onPlayerTrade(player, SHOPEVENT_BUY, onBuy, it.id, subType, amount, ignoreCap, inBackpacks); return true; } bool Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; int32_t onBuy, onSell; Npc* merchant = player->getShopOwner(onBuy, onSell); if(!merchant) return false; const ItemType& it = Item::items.getItemIdByClientId(spriteId); if(!it.id) return false; uint8_t subType = count; if(it.isFluidContainer() && count < uint8_t(sizeof(reverseFluidMap) / sizeof(int8_t))) subType = reverseFluidMap[count]; if(!player->canShopItem(it.id, subType, SHOPEVENT_SELL)) return false; merchant->onPlayerTrade(player, SHOPEVENT_SELL, onSell, it.id, subType, amount); return true; } bool Game::playerCloseShop(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(player == NULL || player->isRemoved()) return false; player->closeShopWindow(); return true; } bool Game::playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count) { Player* player = getPlayerByID(playerId); if(player == NULL || player->isRemoved()) return false; const ItemType& it = Item::items.getItemIdByClientId(spriteId); if(it.id == 0) return false; int32_t subType = count; if(it.isFluidContainer() && count < uint8_t(sizeof(reverseFluidMap) / sizeof(int8_t))) subType = reverseFluidMap[count]; std::stringstream ss; ss << "You see " << Item::getDescription(it, 1, NULL, subType); if(player->hasCustomFlag(PlayerCustomFlag_CanSeeItemDetails)) ss << std::endl << "ItemID: [" << it.id << "]."; player->sendTextMessage(MSG_INFO_DESCR, ss.str()); return true; } bool Game::playerLookAt(uint32_t playerId, const Position& pos, uint16_t spriteId, int16_t stackpos) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Thing* thing = internalGetThing(player, pos, stackpos, spriteId, STACKPOS_LOOK); if(!thing) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } Position thingPos = pos; if(pos.x == 0xFFFF) thingPos = thing->getPosition(); if(!player->canSee(thingPos)) { player->sendCancelMessage(RET_NOTPOSSIBLE); return false; } Position playerPos = player->getPosition(); int32_t lookDistance = -1; if(thing != player) { lookDistance = std::max(std::abs(playerPos.x - thingPos.x), std::abs(playerPos.y - thingPos.y)); if(playerPos.z != thingPos.z) lookDistance = lookDistance + 9 + 6; } bool deny = false; CreatureEventList lookEvents = player->getCreatureEvents(CREATURE_EVENT_LOOK); for(CreatureEventList::iterator it = lookEvents.begin(); it != lookEvents.end(); ++it) { if(!(*it)->executeLook(player, thing, thingPos, stackpos, lookDistance)) deny = true; } if(deny) return false; std::stringstream ss; ss << "You see " << thing->getDescription(lookDistance); if(player->hasCustomFlag(PlayerCustomFlag_CanSeeItemDetails)) { if(Item* item = thing->getItem()) { ss << std::endl << "ItemID: [" << item->getID() << "]"; if(item->getActionId() > 0) ss << ", ActionID: [" << item->getActionId() << "]"; if(item->getUniqueId() > 0) ss << ", UniqueID: [" << item->getUniqueId() << "]"; ss << "."; const ItemType& it = Item::items[item->getID()]; if(it.transformEquipTo) ss << std::endl << "TransformTo: [" << it.transformEquipTo << "] (onEquip)."; else if(it.transformDeEquipTo) ss << std::endl << "TransformTo: [" << it.transformDeEquipTo << "] (onDeEquip)."; else if(it.decayTo != -1) ss << std::endl << "DecayTo: [" << it.decayTo << "]."; } } if(player->hasCustomFlag(PlayerCustomFlag_CanSeeCreatureDetails)) { if(const Creature* creature = thing->getCreature()) { ss << std::endl << "Health: [" << creature->getHealth() << " / " << creature->getMaxHealth() << "]"; if(creature->getMaxMana() > 0) ss << ", Mana: [" << creature->getMana() << " / " << creature->getMaxMana() << "]"; ss << "."; if(const Player* destPlayer = creature->getPlayer()) { ss << std::endl << "IP: " << convertIPAddress(destPlayer->getIP()) << ", Client: " << destPlayer->getClientVersion() << "."; if(destPlayer->isGhost()) ss << std::endl << "* Ghost mode *"; } } } if(player->hasCustomFlag(PlayerCustomFlag_CanSeePosition)) ss << std::endl << "Position: {x=" << thingPos.x << ",y=" << thingPos.y << ",z=" << thingPos.z << "}"; player->sendTextMessage(MSG_INFO_DESCR, ss.str()); return true; } bool Game::playerQuests(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; player->sendQuests(); return true; } bool Game::playerQuestInfo(uint32_t playerId, uint16_t questId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Quest* quest = Quests::getInstance()->getQuestById(questId); if(!quest) return false; player->sendQuestInfo(quest); return true; } bool Game::playerCancelAttackAndFollow(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; playerSetAttackedCreature(playerId, 0); playerFollowCreature(playerId, 0); player->stopWalk(); return true; } bool Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(player->getAttackedCreature() && !creatureId) { player->setAttackedCreature(NULL); player->sendCancelTarget(); return true; } Creature* attackCreature = getCreatureByID(creatureId); if(!attackCreature) { player->setAttackedCreature(NULL); player->sendCancelTarget(); return false; } ReturnValue ret = Combat::canTargetCreature(player, attackCreature); if(ret != RET_NOERROR) { player->sendCancelMessage(ret); player->sendCancelTarget(); player->setAttackedCreature(NULL); return false; } player->setAttackedCreature(attackCreature); return true; } bool Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Creature* followCreature = NULL; if(creatureId) followCreature = getCreatureByID(creatureId); player->setAttackedCreature(NULL); return player->setFollowCreature(followCreature); } bool Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, secureMode_t secureMode) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; player->setFightMode(fightMode); player->setChaseMode(chaseMode); player->setSecureMode(secureMode); return true; } bool Game::playerRequestAddVip(uint32_t playerId, const std::string& vipName) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; uint32_t guid; bool specialVip; std::string name = vipName; if(!IOLoginData::getInstance()->getGuidByNameEx(guid, specialVip, name)) { player->sendTextMessage(MSG_STATUS_SMALL, "A player with that name does not exist."); return false; } if(specialVip && !player->hasFlag(PlayerFlag_SpecialVIP)) { player->sendTextMessage(MSG_STATUS_SMALL, "You cannot add this player."); return false; } bool online = false; if(Player* target = getPlayerByName(name)) online = player->canSeeCreature(target); return player->addVIP(guid, name, online); } bool Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; player->removeVIP(guid); return true; } bool Game::playerTurn(uint32_t playerId, Direction dir) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(internalCreatureTurn(player, dir)) { player->setIdleTime(0); return true; } if(player->getDirection() != dir || !player->hasCustomFlag(PlayerCustomFlag_CanTurnhop)) return false; Position pos = getNextPosition(dir, player->getPosition()); Tile* tile = map->getTile(pos); if(!tile || !tile->ground) return false; player->setIdleTime(0); ReturnValue ret = tile->__queryAdd(0, player, 1, FLAG_IGNOREBLOCKITEM); if(ret != RET_NOTENOUGHROOM && (ret != RET_NOTPOSSIBLE || player->hasCustomFlag(PlayerCustomFlag_CanMoveAnywhere)) && (ret != RET_PLAYERISNOTINVITED || player->hasFlag(PlayerFlag_CanEditHouses))) return internalTeleport(player, pos, true); player->sendCancelMessage(ret); return false; } bool Game::playerRequestOutfit(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; player->sendOutfitWindow(); return true; } bool Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(!player->changeOutfit(outfit, true)) return false; player->setIdleTime(0); if(!player->hasCondition(CONDITION_OUTFIT, -1)) internalCreatureChangeOutfit(player, outfit); return true; } bool Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, const std::string& receiver, const std::string& text) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; uint32_t muteTime = 0; bool muted = player->isMuted(channelId, type, muteTime); if(muted) { char buffer[75]; sprintf(buffer, "You are still muted for %d seconds.", muteTime); player->sendTextMessage(MSG_STATUS_SMALL, buffer); return false; } if(player->isAccountManager()) { player->removeMessageBuffer(); return internalCreatureSay(player, SPEAK_SAY, text, false); } if(g_talkActions->onPlayerSay(player, type == SPEAK_SAY ? CHANNEL_DEFAULT : channelId, text, false)) return true; if(!muted) { ReturnValue ret = RET_NOERROR; if(!muteTime) { ret = g_spells->onPlayerSay(player, text); if(ret == RET_NOERROR || (ret == RET_NEEDEXCHANGE && !g_config.getBool(ConfigManager::BUFFER_SPELL_FAILURE))) return true; } player->removeMessageBuffer(); if(ret == RET_NEEDEXCHANGE) return true; } switch(type) { case SPEAK_SAY: return internalCreatureSay(player, SPEAK_SAY, text, false); case SPEAK_WHISPER: return playerWhisper(player, text); case SPEAK_YELL: return playerYell(player, text); case SPEAK_PRIVATE: case SPEAK_PRIVATE_RED: case SPEAK_RVR_ANSWER: return playerSpeakTo(player, type, receiver, text); case SPEAK_CHANNEL_O: case SPEAK_CHANNEL_Y: case SPEAK_CHANNEL_RN: case SPEAK_CHANNEL_RA: case SPEAK_CHANNEL_W: { if(playerTalkToChannel(player, type, text, channelId)) return true; return playerSay(playerId, 0, SPEAK_SAY, receiver, text); } case SPEAK_PRIVATE_PN: return playerSpeakToNpc(player, text); case SPEAK_BROADCAST: return playerBroadcastMessage(player, SPEAK_BROADCAST, text); case SPEAK_RVR_CHANNEL: return playerReportRuleViolation(player, text); case SPEAK_RVR_CONTINUE: return playerContinueReport(player, text); default: break; } return false; } bool Game::playerWhisper(Player* player, const std::string& text) { SpectatorVec list; SpectatorVec::const_iterator it; getSpectators(list, player->getPosition(), false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); //send to client Player* tmpPlayer = NULL; for(it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendCreatureSay(player, SPEAK_WHISPER, text); } //event method for(it = list.begin(); it != list.end(); ++it) (*it)->onCreatureSay(player, SPEAK_WHISPER, text); return true; } bool Game::playerYell(Player* player, const std::string& text) { if(player->getLevel() <= 1) { player->sendTextMessage(MSG_STATUS_SMALL, "You may not yell as long as you are on level 1."); return true; } if(player->hasCondition(CONDITION_MUTED, 1)) { player->sendCancelMessage(RET_YOUAREEXHAUSTED); return true; } if(!player->hasFlag(PlayerFlag_CannotBeMuted)) { if(Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_MUTED, 30000, 0, false, 1)) player->addCondition(condition); } internalCreatureSay(player, SPEAK_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 || toPlayer->isRemoved()) { player->sendTextMessage(MSG_STATUS_SMALL, "A player with this name is not online."); return false; } bool canSee = player->canSeeCreature(toPlayer); if(toPlayer->hasCondition(CONDITION_GAMEMASTER, GAMEMASTER_IGNORE) && !player->hasFlag(PlayerFlag_CannotBeMuted)) { char buffer[70]; if(!canSee) sprintf(buffer, "A player with this name is not online."); else sprintf(buffer, "Sorry, %s is currently ignoring private messages.", toPlayer->getName().c_str()); player->sendTextMessage(MSG_STATUS_SMALL, buffer); return false; } if(type == SPEAK_PRIVATE_RED && !player->hasFlag(PlayerFlag_CanTalkRedPrivate)) type = SPEAK_PRIVATE; toPlayer->sendCreatureSay(player, type, text); toPlayer->onCreatureSay(player, type, text); if(!canSee) { player->sendTextMessage(MSG_STATUS_SMALL, "A player with this name is not online."); return false; } char buffer[80]; sprintf(buffer, "Message sent to %s.", toPlayer->getName().c_str()); player->sendTextMessage(MSG_STATUS_SMALL, buffer); return true; } bool Game::playerTalkToChannel(Player* player, SpeakClasses type, const std::string& text, uint16_t channelId) { switch(type) { case SPEAK_CHANNEL_Y: { if(channelId == CHANNEL_HELP && player->hasFlag(PlayerFlag_TalkOrangeHelpChannel)) type = SPEAK_CHANNEL_O; break; } case SPEAK_CHANNEL_O: { if(channelId != CHANNEL_HELP || !player->hasFlag(PlayerFlag_TalkOrangeHelpChannel)) type = SPEAK_CHANNEL_Y; break; } case SPEAK_CHANNEL_RN: { if(!player->hasFlag(PlayerFlag_CanTalkRedChannel)) type = SPEAK_CHANNEL_Y; break; } case SPEAK_CHANNEL_RA: { if(!player->hasFlag(PlayerFlag_CanTalkRedChannelAnonymous)) type = SPEAK_CHANNEL_Y; break; } default: break; } return g_chat.talkToChannel(player, type, text, channelId); } bool Game::playerSpeakToNpc(Player* player, const std::string& text) { SpectatorVec list; SpectatorVec::iterator it; getSpectators(list, player->getPosition()); //send to npcs only Npc* tmpNpc = NULL; for(it = list.begin(); it != list.end(); ++it) { if((tmpNpc = (*it)->getNpc())) (*it)->onCreatureSay(player, SPEAK_PRIVATE_PN, text); } return true; } bool Game::playerReportRuleViolation(Player* player, const std::string& text) { //Do not allow reports on multiclones worlds since reports are name-based if(g_config.getNumber(ConfigManager::ALLOW_CLONES)) { player->sendTextMessage(MSG_INFO_DESCR, "Rule violation reports are disabled."); return false; } cancelRuleViolation(player); boost::shared_ptr rvr(new RuleViolation(player, text, time(NULL))); ruleViolations[player->getID()] = rvr; ChatChannel* channel = g_chat.getChannelById(CHANNEL_RVR); if(!channel) return false; for(UsersMap::const_iterator it = channel->getUsers().begin(); it != channel->getUsers().end(); ++it) it->second->sendToChannel(player, SPEAK_RVR_CHANNEL, text, CHANNEL_RVR, rvr->time); return true; } bool Game::playerContinueReport(Player* player, const std::string& text) { RuleViolationsMap::iterator it = ruleViolations.find(player->getID()); if(it == ruleViolations.end()) return false; RuleViolation& rvr = *it->second; Player* toPlayer = rvr.gamemaster; if(!toPlayer) return false; toPlayer->sendCreatureSay(player, SPEAK_RVR_CONTINUE, text); player->sendTextMessage(MSG_STATUS_SMALL, "Message sent to Gamemaster."); return true; } //-- bool Game::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) { return map->canThrowObjectTo(fromPos, toPos, checkLineOfSight, rangex, rangey); } bool Game::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) { return map->isSightClear(fromPos, toPos, floorCheck); } bool Game::internalCreatureTurn(Creature* creature, Direction dir) { bool deny = false; CreatureEventList directionEvents = creature->getCreatureEvents(CREATURE_EVENT_DIRECTION); for(CreatureEventList::iterator it = directionEvents.begin(); it != directionEvents.end(); ++it) { if(!(*it)->executeDirection(creature, creature->getDirection(), dir) && !deny) deny = true; } if(deny || creature->getDirection() == dir) return false; creature->setDirection(dir); const SpectatorVec& list = getSpectators(creature->getPosition()); SpectatorVec::const_iterator it; //send to client Player* tmpPlayer = NULL; for(it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendCreatureTurn(creature); } //event method for(it = list.begin(); it != list.end(); ++it) (*it)->onCreatureTurn(creature); return true; } bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, bool ghostMode, SpectatorVec* spectators/* = NULL*/, Position* pos/* = NULL*/) { Player* player = creature->getPlayer(); if(player && player->isAccountManager()) { player->manageAccount(text); return true; } Position destPos = creature->getPosition(); if(pos) destPos = (*pos); SpectatorVec list; SpectatorVec::const_iterator it; if(!spectators || !spectators->size()) { // 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 != SPEAK_YELL && type != SPEAK_MONSTER_YELL) getSpectators(list, destPos, false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); else getSpectators(list, destPos, false, true, 18, 18, 14, 14); } else list = (*spectators); //send to client Player* tmpPlayer = NULL; for(it = list.begin(); it != list.end(); ++it) { if(!(tmpPlayer = (*it)->getPlayer())) continue; if(!ghostMode || tmpPlayer->canSeeCreature(creature)) tmpPlayer->sendCreatureSay(creature, type, text, &destPos); } //event method for(it = list.begin(); it != list.end(); ++it) (*it)->onCreatureSay(creature, type, text, &destPos); return true; } bool Game::getPathTo(const Creature* creature, const Position& destPos, std::list& listDir, int32_t maxSearchDist /*= -1*/) { return map->getPathTo(creature, destPos, listDir, maxSearchDist); } bool Game::getPathToEx(const Creature* creature, const Position& targetPos, std::list& dirList, const FindPathParams& fpp, uint32_t nodesLimit /*= 100 */) { return map->getPathMatching(creature, dirList, FrozenPathingConditionCall(targetPos), fpp, nodesLimit); } bool Game::getPathToEx(const Creature* creature, const Position& targetPos, std::list& dirList, uint32_t minTargetDist, uint32_t maxTargetDist, bool fullPathSearch /*= true*/, bool clearSight /*= true*/, int32_t maxSearchDist /*= -1*/, uint32_t nodesLimit /*= 100 */) { FindPathParams fpp; fpp.fullPathSearch = fullPathSearch; fpp.maxSearchDist = maxSearchDist; fpp.clearSight = clearSight; fpp.minTargetDist = minTargetDist; fpp.maxTargetDist = maxTargetDist; return getPathToEx(creature, targetPos, dirList, fpp); } 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->getPathToFollowCreature(); } void Game::checkCreatureAttack(uint32_t creatureId) { Creature* creature = getCreatureByID(creatureId); if(creature && creature->getHealth() > 0) creature->onAttacking(0); } void Game::addCreatureCheck(Creature* creature) { if(creature->isRemoved()) return; creature->checked = true; if(creature->checkVector >= 0) //already in a vector, or about to be added return; toAddCheckCreatureVector.push_back(creature); creature->checkVector = random_range(0, EVENT_CREATURECOUNT - 1); creature->addRef(); } void Game::removeCreatureCheck(Creature* creature) { if(creature->checkVector == -1) //not in any vector return; creature->checked = false; } void Game::checkCreatures() { Scheduler::getInstance().addEvent(createSchedulerTask( EVENT_CHECK_CREATURE_INTERVAL, boost::bind(&Game::checkCreatures, this))); checkCreatureLastIndex++; if(checkCreatureLastIndex == EVENT_CREATURECOUNT) checkCreatureLastIndex = 0; std::vector::iterator it; for(it = toAddCheckCreatureVector.begin(); it != toAddCheckCreatureVector.end(); ++it) checkCreatureVectors[(*it)->checkVector].push_back(*it); toAddCheckCreatureVector.clear(); std::vector& checkCreatureVector = checkCreatureVectors[checkCreatureLastIndex]; for(it = checkCreatureVector.begin(); it != checkCreatureVector.end();) { if((*it)->checked) { if((*it)->getHealth() > 0 || !(*it)->onDeath()) (*it)->onThink(EVENT_CREATURE_THINK_INTERVAL); ++it; } else { (*it)->checkVector = -1; freeThing(*it); it = checkCreatureVector.erase(it); } } cleanup(); } void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta) { int32_t varSpeed = creature->getSpeed() - creature->getBaseSpeed(); varSpeed += varSpeedDelta; creature->setSpeed(varSpeed); const SpectatorVec& list = getSpectators(creature->getPosition()); SpectatorVec::const_iterator it; //send to client Player* tmpPlayer = NULL; for(it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendChangeSpeed(creature, creature->getStepSpeed()); } } void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outfit, bool forced/* = false*/) { if(!forced) { bool deny = false; CreatureEventList outfitEvents = creature->getCreatureEvents(CREATURE_EVENT_OUTFIT); for(CreatureEventList::iterator it = outfitEvents.begin(); it != outfitEvents.end(); ++it) { if(!(*it)->executeOutfit(creature, creature->getCurrentOutfit(), outfit) && !deny) deny = true; } if(deny || creature->getCurrentOutfit() == outfit) return; } creature->setCurrentOutfit(outfit); const SpectatorVec& list = getSpectators(creature->getPosition()); SpectatorVec::const_iterator it; //send to client Player* tmpPlayer = NULL; for(it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendCreatureChangeOutfit(creature, outfit); } //event method for(it = list.begin(); it != list.end(); ++it) (*it)->onCreatureChangeOutfit(creature, outfit); } void Game::internalCreatureChangeVisible(Creature* creature, Visible_t visible) { const SpectatorVec& list = getSpectators(creature->getPosition()); SpectatorVec::const_iterator it; //send to client Player* tmpPlayer = NULL; for(it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendCreatureChangeVisible(creature, visible); } //event method for(it = list.begin(); it != list.end(); ++it) (*it)->onCreatureChangeVisible(creature, visible); } void Game::changeLight(const Creature* creature) { const SpectatorVec& list = getSpectators(creature->getPosition()); //send to client Player* tmpPlayer = NULL; for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendCreatureLight(creature); } } bool Game::combatBlockHit(CombatType_t combatType, Creature* attacker, Creature* target, int32_t& healthChange, bool checkDefense, bool checkArmor) { if(healthChange > 0) return false; const Position& targetPos = target->getPosition(); const SpectatorVec& list = getSpectators(targetPos); if(!target->isAttackable() || Combat::canDoCombat(attacker, target) != RET_NOERROR) { addMagicEffect(list, targetPos, MAGIC_EFFECT_POFF, target->isGhost()); return true; } int32_t damage = -healthChange; BlockType_t blockType = target->blockHit(attacker, combatType, damage, checkDefense, checkArmor); healthChange = -damage; if(blockType == BLOCK_DEFENSE) { addMagicEffect(list, targetPos, MAGIC_EFFECT_POFF); return true; } else if(blockType == BLOCK_ARMOR) { addMagicEffect(list, targetPos, MAGIC_EFFECT_BLOCKHIT); return true; } else if(blockType != BLOCK_IMMUNITY) return false; MagicEffect_t effect = MAGIC_EFFECT_NONE; switch(combatType) { case COMBAT_UNDEFINEDDAMAGE: break; case COMBAT_ENERGYDAMAGE: case COMBAT_FIREDAMAGE: case COMBAT_PHYSICALDAMAGE: case COMBAT_ICEDAMAGE: case COMBAT_DEATHDAMAGE: case COMBAT_EARTHDAMAGE: case COMBAT_HOLYDAMAGE: { effect = MAGIC_EFFECT_BLOCKHIT; break; } default: { effect = MAGIC_EFFECT_POFF; break; } } addMagicEffect(list, targetPos, effect); return true; } bool Game::combatChangeHealth(CombatType_t combatType, Creature* attacker, Creature* target, int32_t healthChange, MagicEffect_t hitEffect/* = MAGIC_EFFECT_UNKNOWN*/, TextColor_t hitColor/* = TEXTCOLOR_UNKNOWN*/, bool force/* = false*/) { const Position& targetPos = target->getPosition(); if(healthChange > 0) { if(!force && target->getHealth() <= 0) return false; bool deny = false; CreatureEventList statsChangeEvents = target->getCreatureEvents(CREATURE_EVENT_STATSCHANGE); for(CreatureEventList::iterator it = statsChangeEvents.begin(); it != statsChangeEvents.end(); ++it) { if(!(*it)->executeStatsChange(target, attacker, STATSCHANGE_HEALTHGAIN, combatType, healthChange)) deny = true; } if(deny) return false; target->gainHealth(attacker, healthChange); if(g_config.getBool(ConfigManager::SHOW_HEALING_DAMAGE) && !target->isGhost() && (g_config.getBool(ConfigManager::SHOW_HEALING_DAMAGE_MONSTER) || !target->getMonster())) { char buffer[20]; sprintf(buffer, "+%d", healthChange); const SpectatorVec& list = getSpectators(targetPos); if(combatType != COMBAT_HEALING) addMagicEffect(list, targetPos, MAGIC_EFFECT_WRAPS_BLUE); addAnimatedText(list, targetPos, TEXTCOLOR_GREEN, buffer); } } else { const SpectatorVec& list = getSpectators(targetPos); if(!target->isAttackable() || Combat::canDoCombat(attacker, target) != RET_NOERROR) { addMagicEffect(list, targetPos, MAGIC_EFFECT_POFF); return true; } int32_t damage = -healthChange; if(damage != 0) { if(target->hasCondition(CONDITION_MANASHIELD) && combatType != COMBAT_UNDEFINEDDAMAGE) { int32_t manaDamage = std::min(target->getMana(), damage); damage = std::max((int32_t)0, damage - manaDamage); if(manaDamage != 0) { bool deny = false; CreatureEventList statsChangeEvents = target->getCreatureEvents(CREATURE_EVENT_STATSCHANGE); for(CreatureEventList::iterator it = statsChangeEvents.begin(); it != statsChangeEvents.end(); ++it) { if(!(*it)->executeStatsChange(target, attacker, STATSCHANGE_MANALOSS, combatType, manaDamage)) deny = true; } if(deny) return false; target->drainMana(attacker, combatType, manaDamage); char buffer[20]; sprintf(buffer, "%d", manaDamage); addMagicEffect(list, targetPos, MAGIC_EFFECT_LOSE_ENERGY); addAnimatedText(list, targetPos, TEXTCOLOR_BLUE, buffer); } } damage = std::min(target->getHealth(), damage); if(damage > 0) { bool deny = false; CreatureEventList statsChangeEvents = target->getCreatureEvents(CREATURE_EVENT_STATSCHANGE); for(CreatureEventList::iterator it = statsChangeEvents.begin(); it != statsChangeEvents.end(); ++it) { if(!(*it)->executeStatsChange(target, attacker, STATSCHANGE_HEALTHLOSS, combatType, damage)) deny = true; } if(deny) return false; target->drainHealth(attacker, combatType, damage); addCreatureHealth(list, target); TextColor_t textColor = TEXTCOLOR_NONE; MagicEffect_t magicEffect = MAGIC_EFFECT_NONE; switch(combatType) { case COMBAT_PHYSICALDAMAGE: { Item* splash = NULL; switch(target->getRace()) { case RACE_VENOM: textColor = TEXTCOLOR_LIGHTGREEN; magicEffect = MAGIC_EFFECT_POISON; splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_GREEN); break; case RACE_BLOOD: textColor = TEXTCOLOR_RED; magicEffect = MAGIC_EFFECT_DRAW_BLOOD; splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD); break; case RACE_UNDEAD: textColor = TEXTCOLOR_GREY; magicEffect = MAGIC_EFFECT_HIT_AREA; break; case RACE_FIRE: textColor = TEXTCOLOR_ORANGE; magicEffect = MAGIC_EFFECT_DRAW_BLOOD; break; case RACE_ENERGY: textColor = TEXTCOLOR_PURPLE; magicEffect = MAGIC_EFFECT_PURPLEENERGY; break; default: break; } if(splash) { internalAddItem(NULL, target->getTile(), splash, INDEX_WHEREEVER, FLAG_NOLIMIT); startDecay(splash); } break; } case COMBAT_ENERGYDAMAGE: { textColor = TEXTCOLOR_PURPLE; magicEffect = MAGIC_EFFECT_ENERGY_DAMAGE; break; } case COMBAT_EARTHDAMAGE: { textColor = TEXTCOLOR_LIGHTGREEN; magicEffect = MAGIC_EFFECT_POISON_RINGS; break; } case COMBAT_DROWNDAMAGE: { textColor = TEXTCOLOR_LIGHTBLUE; magicEffect = MAGIC_EFFECT_LOSE_ENERGY; break; } case COMBAT_FIREDAMAGE: { textColor = TEXTCOLOR_ORANGE; magicEffect = MAGIC_EFFECT_HITBY_FIRE; break; } case COMBAT_ICEDAMAGE: { textColor = TEXTCOLOR_TEAL; magicEffect = MAGIC_EFFECT_ICEATTACK; break; } case COMBAT_HOLYDAMAGE: { textColor = TEXTCOLOR_YELLOW; magicEffect = MAGIC_EFFECT_HOLYDAMAGE; break; } case COMBAT_DEATHDAMAGE: { textColor = TEXTCOLOR_DARKRED; magicEffect = MAGIC_EFFECT_SMALLCLOUDS; break; } case COMBAT_LIFEDRAIN: { textColor = TEXTCOLOR_RED; magicEffect = MAGIC_EFFECT_WRAPS_RED; break; } default: break; } if(hitEffect != MAGIC_EFFECT_UNKNOWN) magicEffect = hitEffect; if(hitColor != TEXTCOLOR_UNKNOWN) textColor = hitColor; if(textColor < TEXTCOLOR_NONE && magicEffect < MAGIC_EFFECT_NONE) { char buffer[20]; sprintf(buffer, "%d", damage); addMagicEffect(list, targetPos, magicEffect); addAnimatedText(list, targetPos, textColor, buffer); } } } } return true; } bool Game::combatChangeMana(Creature* attacker, Creature* target, int32_t manaChange) { const Position& targetPos = target->getPosition(); if(manaChange > 0) { bool deny = false; CreatureEventList statsChangeEvents = target->getCreatureEvents(CREATURE_EVENT_STATSCHANGE); for(CreatureEventList::iterator it = statsChangeEvents.begin(); it != statsChangeEvents.end(); ++it) { if(!(*it)->executeStatsChange(target, attacker, STATSCHANGE_MANAGAIN, COMBAT_HEALING, manaChange)) deny = true; } if(deny) return false; target->changeMana(manaChange); if(g_config.getBool(ConfigManager::SHOW_HEALING_DAMAGE) && !target->isGhost() && (g_config.getBool(ConfigManager::SHOW_HEALING_DAMAGE_MONSTER) || !target->getMonster())) { char buffer[20]; sprintf(buffer, "+%d", manaChange); const SpectatorVec& list = getSpectators(targetPos); addAnimatedText(list, targetPos, TEXTCOLOR_DARKPURPLE, buffer); } } else { const SpectatorVec& list = getSpectators(targetPos); if(!target->isAttackable() || Combat::canDoCombat(attacker, target) != RET_NOERROR) { addMagicEffect(list, targetPos, MAGIC_EFFECT_POFF); return false; } int32_t manaLoss = std::min(target->getMana(), -manaChange); BlockType_t blockType = target->blockHit(attacker, COMBAT_MANADRAIN, manaLoss); if(blockType != BLOCK_NONE) { addMagicEffect(list, targetPos, MAGIC_EFFECT_POFF); return false; } if(manaLoss > 0) { bool deny = false; CreatureEventList statsChangeEvents = target->getCreatureEvents(CREATURE_EVENT_STATSCHANGE); for(CreatureEventList::iterator it = statsChangeEvents.begin(); it != statsChangeEvents.end(); ++it) { if(!(*it)->executeStatsChange(target, attacker, STATSCHANGE_MANALOSS, COMBAT_UNDEFINEDDAMAGE, manaChange)) deny = true; } if(deny) return false; target->drainMana(attacker, COMBAT_MANADRAIN, manaLoss); char buffer[20]; sprintf(buffer, "%d", manaLoss); addAnimatedText(list, targetPos, TEXTCOLOR_BLUE, buffer); } } return true; } void Game::addCreatureHealth(const Creature* target) { const SpectatorVec& list = getSpectators(target->getPosition()); addCreatureHealth(list, target); } void Game::addCreatureHealth(const SpectatorVec& list, const Creature* target) { Player* player = NULL; for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it) { if((player = (*it)->getPlayer())) player->sendCreatureHealth(target); } } void Game::addAnimatedText(const Position& pos, uint8_t textColor, const std::string& text) { const SpectatorVec& list = getSpectators(pos); addAnimatedText(list, pos, textColor, text); } void Game::addAnimatedText(const SpectatorVec& list, const Position& pos, uint8_t textColor, const std::string& text) { Player* player = NULL; for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it) { if((player = (*it)->getPlayer())) player->sendAnimatedText(pos, textColor, text); } } void Game::addMagicEffect(const Position& pos, uint8_t effect, bool ghostMode /* = false */) { if(ghostMode) return; const SpectatorVec& list = getSpectators(pos); addMagicEffect(list, pos, effect); } void Game::addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect, bool ghostMode/* = false*/) { if(ghostMode) return; Player* player = NULL; for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it) { if((player = (*it)->getPlayer())) player->sendMagicEffect(pos, effect); } } void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) { SpectatorVec list; getSpectators(list, fromPos, false); getSpectators(list, toPos, true); addDistanceEffect(list, fromPos, toPos, effect); } void Game::addDistanceEffect(const SpectatorVec& list, const Position& fromPos, const Position& toPos, uint8_t effect) { Player* player = NULL; for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it) { if((player = (*it)->getPlayer())) player->sendDistanceShoot(fromPos, toPos, effect); } } void Game::startDecay(Item* item) { if(!item || !item->canDecay() || item->getDecaying() == DECAYING_TRUE) return; if(item->getDuration() > 0) { item->addRef(); item->setDecaying(DECAYING_TRUE); toDecayItems.push_back(item); } else internalDecayItem(item); } void Game::internalDecayItem(Item* item) { const ItemType& it = Item::items.getItemType(item->getID()); if(it.decayTo) { Item* newItem = transformItem(item, it.decayTo); startDecay(newItem); } else { ReturnValue ret = internalRemoveItem(NULL, item); if(ret != RET_NOERROR) std::cout << "> DEBUG: internalDecayItem failed, error code: " << (int32_t)ret << ", item id: " << item->getID() << std::endl; } } void Game::checkDecay() { Scheduler::getInstance().addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, boost::bind(&Game::checkDecay, this))); size_t bucket = (lastBucket + 1) % EVENT_DECAYBUCKETS; for(DecayList::iterator it = decayItems[bucket].begin(); it != decayItems[bucket].end();) { Item* item = *it; int32_t decreaseTime = EVENT_DECAYINTERVAL * EVENT_DECAYBUCKETS; if(item->getDuration() - decreaseTime < 0) decreaseTime = item->getDuration(); item->decreaseDuration(decreaseTime); if(!item->canDecay()) { item->setDecaying(DECAYING_FALSE); freeThing(item); it = decayItems[bucket].erase(it); continue; } int32_t dur = item->getDuration(); if(dur <= 0) { it = decayItems[bucket].erase(it); internalDecayItem(item); freeThing(item); } else if(dur < EVENT_DECAYINTERVAL * EVENT_DECAYBUCKETS) { it = decayItems[bucket].erase(it); size_t newBucket = (bucket + ((dur + EVENT_DECAYINTERVAL / 2) / 1000)) % EVENT_DECAYBUCKETS; if(newBucket == bucket) { internalDecayItem(item); freeThing(item); } else decayItems[newBucket].push_back(item); } else ++it; } lastBucket = bucket; cleanup(); } void Game::checkLight() { Scheduler::getInstance().addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, boost::bind(&Game::checkLight, this))); lightHour = lightHour + lightHourDelta; if(lightHour > 1440) lightHour = 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(lightInfo); for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) it->second->sendWorldLight(lightInfo); } } #ifdef __WAR_SYSTEM__ void Game::checkWars() { IOGuild::getInstance()->checkWars(); checkWarsEvent = Scheduler::getInstance().addEvent(createSchedulerTask(EVENT_WARSINTERVAL, boost::bind(&Game::checkWars, this))); } #endif void Game::getWorldLightInfo(LightInfo& lightInfo) { lightInfo.level = lightLevel; lightInfo.color = 0xD7; } bool Game::cancelRuleViolation(Player* player) { RuleViolationsMap::iterator it = ruleViolations.find(player->getID()); if(it == ruleViolations.end()) return false; Player* gamemaster = it->second->gamemaster; if(!it->second->isOpen && gamemaster) //Send to the responser gamemaster->sendRuleViolationCancel(player->getName()); else if(ChatChannel* channel = g_chat.getChannelById(CHANNEL_RVR)) { UsersMap tmpMap = channel->getUsers(); for(UsersMap::iterator tit = tmpMap.begin(); tit != tmpMap.end(); ++tit) tit->second->sendRemoveReport(player->getName()); } //Now erase it ruleViolations.erase(it); return true; } bool Game::closeRuleViolation(Player* player) { RuleViolationsMap::iterator it = ruleViolations.find(player->getID()); if(it == ruleViolations.end()) return false; ruleViolations.erase(it); player->sendLockRuleViolation(); if(ChatChannel* channel = g_chat.getChannelById(CHANNEL_RVR)) { UsersMap tmpMap = channel->getUsers(); for(UsersMap::iterator tit = tmpMap.begin(); tit != tmpMap.end(); ++tit) tit->second->sendRemoveReport(player->getName()); } return true; } void Game::updateCreatureSkull(Creature* creature) { const SpectatorVec& list = getSpectators(creature->getPosition()); //send to client Player* tmpPlayer = NULL; for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendCreatureSkull(creature); } } void Game::updateCreatureShield(Creature* creature) { const SpectatorVec& list = getSpectators(creature->getPosition()); //send to client Player* tmpPlayer = NULL; for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendCreatureShield(creature); } } void Game::updateCreatureEmblem(Creature* creature) { const SpectatorVec& list = getSpectators(creature->getPosition()); //send to client Player* tmpPlayer = NULL; for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it) { if((tmpPlayer = (*it)->getPlayer())) tmpPlayer->sendCreatureEmblem(creature); } } bool Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Player* invitedPlayer = getPlayerByID(invitedId); if(!invitedPlayer || invitedPlayer->isRemoved() || invitedPlayer->isInviting(player)) return false; if(invitedPlayer->getParty()) { char buffer[90]; sprintf(buffer, "%s is already in a party.", invitedPlayer->getName().c_str()); player->sendTextMessage(MSG_INFO_DESCR, buffer); return false; } Party* party = player->getParty(); if(!party) party = new Party(player); else if(party->getLeader() != player) return false; return party->invitePlayer(invitedPlayer); } bool Game::playerJoinParty(uint32_t playerId, uint32_t leaderId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Player* leader = getPlayerByID(leaderId); if(!leader || leader->isRemoved() || !leader->isInviting(player)) return false; if(!player->getParty()) return leader->getParty()->join(player); player->sendTextMessage(MSG_INFO_DESCR, "You are already in a party."); return false; } bool Game::playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved() || !player->getParty() || player->getParty()->getLeader() != player) return false; Player* invitedPlayer = getPlayerByID(invitedId); if(!invitedPlayer || invitedPlayer->isRemoved() || !player->isInviting(invitedPlayer)) return false; player->getParty()->revokeInvitation(invitedPlayer); return true; } bool Game::playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved() || !player->getParty() || player->getParty()->getLeader() != player) return false; Player* newLeader = getPlayerByID(newLeaderId); if(!newLeader || newLeader->isRemoved() || !player->isPartner(newLeader)) return false; return player->getParty()->passLeadership(newLeader); } bool Game::playerLeaveParty(uint32_t playerId) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(!player->getParty() || player->hasCondition(CONDITION_INFIGHT)) return false; return player->getParty()->leave(player); } bool Game::playerSharePartyExperience(uint32_t playerId, bool activate, uint8_t unknown) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(!player->getParty() || (!player->hasFlag(PlayerFlag_NotGainInFight) && player->hasCondition(CONDITION_INFIGHT))) return false; return player->getParty()->setSharedExperience(player, activate); } bool Game::playerReportBug(uint32_t playerId, std::string comment) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; if(!player->hasFlag(PlayerFlag_CanReportBugs)) return false; CreatureEventList reportBugEvents = player->getCreatureEvents(CREATURE_EVENT_REPORTBUG); for(CreatureEventList::iterator it = reportBugEvents.begin(); it != reportBugEvents.end(); ++it) (*it)->executeReportBug(player, comment); return true; } bool Game::playerViolationWindow(uint32_t playerId, std::string name, uint8_t reason, ViolationAction_t action, std::string comment, std::string statement, uint32_t statementId, bool ipBanishment) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return false; Group* group = player->getGroup(); if(!group) return false; time_t length[3] = {0, 0, 0}; int32_t pos = 0, start = comment.find("{"); while((start = comment.find("{")) > 0 && pos < 4) { std::string::size_type end = comment.find("}", start); if(end == std::string::npos) break; std::string data = comment.substr(start + 1, end - 1); comment = comment.substr(end + 1); ++pos; if(data.empty()) continue; if(data == "delete") { action = ACTION_DELETION; continue; } time_t banTime = time(NULL); StringVec vec = explodeString(";", data); for(StringVec::iterator it = vec.begin(); it != vec.end(); ++it) { StringVec tmp = explodeString(",", *it); uint32_t count = 1; if(tmp.size() > 1) { count = atoi(tmp[1].c_str()); if(!count) count = 1; } if(tmp[0][0] == 's') banTime += count; if(tmp[0][0] == 'm') banTime += count * 60; if(tmp[0][0] == 'h') banTime += count * 3600; if(tmp[0][0] == 'd') banTime += count * 86400; if(tmp[0][0] == 'w') banTime += count * 604800; if(tmp[0][0] == 'm') banTime += count * 2592000; if(tmp[0][0] == 'y') banTime += count * 31536000; } if(action == ACTION_DELETION) length[pos - 2] = banTime; else length[pos - 1] = banTime; } int16_t nameFlags = group->getNameViolationFlags(), statementFlags = group->getStatementViolationFlags(); if((ipBanishment && ((nameFlags & IPBAN_FLAG) != IPBAN_FLAG || (statementFlags & IPBAN_FLAG) != IPBAN_FLAG)) || !(nameFlags & (1 << action) || statementFlags & (1 << action)) || reason > group->getViolationReasons()) { player->sendCancel("You do not have authorization for this action."); return false; } uint32_t commentSize = g_config.getNumber(ConfigManager::MAX_VIOLATIONCOMMENT_SIZE); if(comment.size() > commentSize) { char buffer[90]; sprintf(buffer, "The comment may not exceed limit of %d characters.", commentSize); player->sendCancel(buffer); return false; } toLowerCaseString(name); Player* target = getPlayerByNameEx(name); if(!target || name == "account manager") { player->sendCancel("A player with this name does not exist."); return false; } if(target->hasFlag(PlayerFlag_CannotBeBanned)) { player->sendCancel("You do not have authorization for this action."); return false; } Account account = IOLoginData::getInstance()->loadAccount(target->getAccount(), true); enum KickAction { NONE = 1, KICK = 2, FULL_KICK = 3, } kickAction = FULL_KICK; pos = 1; switch(action) { case ACTION_STATEMENT: { StatementMap::iterator it = g_chat.statementMap.find(statementId); if(it == g_chat.statementMap.end()) { player->sendCancel("Statement has been already reported."); return false; } IOBan::getInstance()->addStatement(target->getGUID(), reason, comment, player->getGUID(), -1, statement); g_chat.statementMap.erase(it); kickAction = NONE; break; } case ACTION_NAMEREPORT: { int64_t banTime = -1; PlayerBan_t tmp = (PlayerBan_t)g_config.getNumber(ConfigManager::NAME_REPORT_TYPE); if(tmp == PLAYERBAN_BANISHMENT) { if(!length[0]) banTime = time(NULL) + g_config.getNumber(ConfigManager::BAN_LENGTH); else banTime = length[0]; } if(!IOBan::getInstance()->addPlayerBanishment(target->getGUID(), banTime, reason, action, comment, player->getGUID(), tmp)) { player->sendCancel("Player has been already reported."); return false; } else if(tmp == PLAYERBAN_BANISHMENT) account.warnings++; kickAction = (KickAction)tmp; break; } case ACTION_NOTATION: { if(!IOBan::getInstance()->addNotation(account.number, reason, comment, player->getGUID(), target->getGUID())) { player->sendCancel("Unable to perform action."); return false; } if(IOBan::getInstance()->getNotationsCount(account.number) < (uint32_t) g_config.getNumber(ConfigManager::NOTATIONS_TO_BAN)) { kickAction = NONE; break; } action = ACTION_BANISHMENT; } case ACTION_BANISHMENT: case ACTION_BANREPORT: { bool deny = action != ACTION_BANREPORT; int64_t banTime = -1; pos = 2; account.warnings++; if(account.warnings >= g_config.getNumber(ConfigManager::WARNINGS_TO_DELETION)) action = ACTION_DELETION; else if(length[0]) banTime = length[0]; else if(account.warnings >= g_config.getNumber(ConfigManager::WARNINGS_TO_FINALBAN)) banTime = time(NULL) + g_config.getNumber(ConfigManager::FINALBAN_LENGTH); else banTime = time(NULL) + g_config.getNumber(ConfigManager::BAN_LENGTH); if(!IOBan::getInstance()->addAccountBanishment(account.number, banTime, reason, action, comment, player->getGUID(), target->getGUID())) { account.warnings--; player->sendCancel("Account is already banned."); return false; } if(deny) break; banTime = -1; PlayerBan_t tmp = (PlayerBan_t)g_config.getNumber(ConfigManager::NAME_REPORT_TYPE); if(tmp == PLAYERBAN_BANISHMENT) { if(!length[1]) banTime = time(NULL) + g_config.getNumber(ConfigManager::FINALBAN_LENGTH); else banTime = length[1]; } IOBan::getInstance()->addPlayerBanishment(target->getGUID(), banTime, reason, action, comment, player->getGUID(), tmp); break; } case ACTION_BANFINAL: case ACTION_BANREPORTFINAL: { bool allow = action == ACTION_BANREPORTFINAL; int64_t banTime = -1; account.warnings++; if(account.warnings >= g_config.getNumber(ConfigManager::WARNINGS_TO_DELETION)) action = ACTION_DELETION; else if(length[0]) banTime = length[0]; else banTime = time(NULL) + g_config.getNumber(ConfigManager::FINALBAN_LENGTH); if(!IOBan::getInstance()->addAccountBanishment(account.number, banTime, reason, action, comment, player->getGUID(), target->getGUID())) { account.warnings--; player->sendCancel("Account is already banned."); return false; } if(action != ACTION_DELETION) account.warnings += (g_config.getNumber(ConfigManager::WARNINGS_TO_FINALBAN) - 1); if(allow) IOBan::getInstance()->addPlayerBanishment(target->getGUID(), -1, reason, action, comment, player->getGUID(), (PlayerBan_t)g_config.getNumber( ConfigManager::NAME_REPORT_TYPE)); break; } case ACTION_DELETION: { //completely internal account.warnings++; if(!IOBan::getInstance()->addAccountBanishment(account.number, -1, reason, ACTION_DELETION, comment, player->getGUID(), target->getGUID())) { account.warnings--; player->sendCancel("Account is currently banned or already deleted."); return false; } break; } default: // these just shouldn't occur in rvw return false; } if(ipBanishment && target->getIP()) { if(!length[pos]) length[pos] = time(NULL) + g_config.getNumber(ConfigManager::IPBANISHMENT_LENGTH); IOBan::getInstance()->addIpBanishment(target->getIP(), length[pos], reason, comment, player->getGUID(), 0xFFFFFFFF); } if(kickAction == FULL_KICK) IOBan::getInstance()->removeNotations(account.number); std::stringstream ss; if(g_config.getBool(ConfigManager::BROADCAST_BANISHMENTS)) ss << player->getName() << " has"; else ss << "You have"; ss << " taken the action \"" << getAction(action, ipBanishment) << "\""; switch(action) { case ACTION_NOTATION: { ss << " (" << (g_config.getNumber(ConfigManager::NOTATIONS_TO_BAN) - IOBan::getInstance()->getNotationsCount( account.number)) << " left to banishment)"; break; } case ACTION_STATEMENT: { ss << " for the statement: \"" << statement << "\""; break; } default: break; } ss << " against: " << name << " (Warnings: " << account.warnings << "), with reason: \"" << getReason( reason) << "\", and comment: \"" << comment << "\"."; if(g_config.getBool(ConfigManager::BROADCAST_BANISHMENTS)) broadcastMessage(ss.str(), MSG_STATUS_WARNING); else player->sendTextMessage(MSG_STATUS_CONSOLE_RED, ss.str()); if(target->isVirtual()) { delete target; target = NULL; } else if(kickAction > NONE) { char buffer[30]; sprintf(buffer, "You have been %s.", (kickAction > KICK ? "banished" : "namelocked")); target->sendTextMessage(MSG_INFO_DESCR, buffer); addMagicEffect(target->getPosition(), MAGIC_EFFECT_WRAPS_GREEN); Scheduler::getInstance().addEvent(createSchedulerTask(1000, boost::bind( &Game::kickPlayer, this, target->getID(), false))); } IOLoginData::getInstance()->saveAccount(account); return true; } void Game::kickPlayer(uint32_t playerId, bool displayEffect) { Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return; player->kickPlayer(displayEffect, true); } bool Game::broadcastMessage(const std::string& text, MessageClasses type) { if(type < MSG_CLASS_FIRST || type > MSG_CLASS_LAST) return false; std::cout << "> Broadcasted message: \"" << text << "\"." << std::endl; for(AutoList::iterator it = Player::autoList.begin(); it != Player::autoList.end(); ++it) it->second->sendTextMessage(type, text); return true; } Position Game::getClosestFreeTile(Creature* creature, Position pos, bool extended/* = false*/, bool ignoreHouse/* = true*/) { PairVector relList; relList.push_back(PositionPair(0, 0)); relList.push_back(PositionPair(-1, -1)); relList.push_back(PositionPair(-1, 0)); relList.push_back(PositionPair(-1, 1)); relList.push_back(PositionPair(0, -1)); relList.push_back(PositionPair(0, 1)); relList.push_back(PositionPair(1, -1)); relList.push_back(PositionPair(1, 0)); relList.push_back(PositionPair(1, 1)); if(extended) { relList.push_back(PositionPair(-2, 0)); relList.push_back(PositionPair(0, -2)); relList.push_back(PositionPair(0, 2)); relList.push_back(PositionPair(2, 0)); } std::random_shuffle(relList.begin() + 1, relList.end()); if(Player* player = creature->getPlayer()) { for(PairVector::iterator it = relList.begin(); it != relList.end(); ++it) { Tile* tile = map->getTile(Position((pos.x + it->first), (pos.y + it->second), pos.z)); if(!tile || !tile->ground) continue; ReturnValue ret = tile->__queryAdd(0, player, 1, FLAG_IGNOREBLOCKITEM); if(ret == RET_NOTENOUGHROOM || (ret == RET_NOTPOSSIBLE && !player->hasCustomFlag(PlayerCustomFlag_CanMoveAnywhere)) || (ret == RET_PLAYERISNOTINVITED && !ignoreHouse && !player->hasFlag(PlayerFlag_CanEditHouses))) continue; return tile->getPosition(); } } else { for(PairVector::iterator it = relList.begin(); it != relList.end(); ++it) { Tile* tile = NULL; if((tile = map->getTile(Position((pos.x + it->first), (pos.y + it->second), pos.z))) && tile->__queryAdd(0, creature, 1, FLAG_IGNOREBLOCKITEM) == RET_NOERROR) return tile->getPosition(); } } return Position(0, 0, 0); } std::string Game::getSearchString(const Position fromPos, const Position toPos, bool fromIsCreature/* = false*/, bool toIsCreature/* = false*/) { /* * When the position is on same level and 0 to 4 squares away, they are "[toIsCreature: standing] next to you" * When the position is on same level and 5 to 100 squares away they are "to the north/west/south/east." * When the position is on any level and 101 to 274 squares away they are "far to the north/west/south/east." * When the position is on any level and 275+ squares away they are "very far to the north/west/south/east." * When the position is not directly north/west/south/east of you they are "((very) far) to the north-west/south-west/south-east/north-east." * When the position is on a lower or higher level and 5 to 100 squares away they are "on a lower (or) higher level to the north/west/south/east." * When the position is on a lower or higher level and 0 to 4 squares away they are "below (or) above you." */ enum distance_t { DISTANCE_BESIDE, DISTANCE_CLOSE, DISTANCE_FAR, DISTANCE_VERYFAR }; enum direction_t { DIR_N, DIR_S, DIR_E, DIR_W, DIR_NE, DIR_NW, DIR_SE, DIR_SW }; enum level_t { LEVEL_HIGHER, LEVEL_LOWER, LEVEL_SAME }; distance_t distance; direction_t direction; level_t level; int32_t dx = fromPos.x - toPos.x, dy = fromPos.y - toPos.y, dz = fromPos.z - toPos.z; if(dz > 0) level = LEVEL_HIGHER; else if(dz < 0) level = LEVEL_LOWER; else level = LEVEL_SAME; if(std::abs(dx) < 5 && std::abs(dy) < 5) distance = DISTANCE_BESIDE; else { int32_t tmp = dx * dx + dy * dy; if(tmp < 10000) distance = DISTANCE_CLOSE; else if(tmp < 75625) distance = DISTANCE_FAR; else distance = DISTANCE_VERYFAR; } float tan; if(dx != 0) tan = (float)dy / (float)dx; else tan = 10.; if(std::abs(tan) < 0.4142) { if(dx > 0) direction = DIR_W; else direction = DIR_E; } else if(std::abs(tan) < 2.4142) { if(tan > 0) { if(dy > 0) direction = DIR_NW; else direction = DIR_SE; } else { if(dx > 0) direction = DIR_SW; else direction = DIR_NE; } } else { if(dy > 0) direction = DIR_N; else direction = DIR_S; } std::stringstream ss; switch(distance) { case DISTANCE_BESIDE: { switch(level) { case LEVEL_SAME: { ss << "is "; if(toIsCreature) ss << "standing "; ss << "next to you"; break; } case LEVEL_HIGHER: { ss << "is above "; if(fromIsCreature) ss << "you"; break; } case LEVEL_LOWER: { ss << "is below "; if(fromIsCreature) ss << "you"; break; } default: break; } break; } case DISTANCE_CLOSE: { switch(level) { case LEVEL_SAME: ss << "is to the"; break; case LEVEL_HIGHER: ss << "is on a higher level to the"; break; case LEVEL_LOWER: ss << "is on a lower level to the"; break; default: break; } break; } case DISTANCE_FAR: ss << "is far to the"; break; case DISTANCE_VERYFAR: ss << "is very far to the"; break; default: break; } if(distance != DISTANCE_BESIDE) { ss << " "; switch(direction) { case DIR_N: ss << "north"; break; case DIR_S: ss << "south"; break; case DIR_E: ss << "east"; break; case DIR_W: ss << "west"; break; case DIR_NE: ss << "north-east"; break; case DIR_NW: ss << "north-west"; break; case DIR_SE: ss << "south-east"; break; case DIR_SW: ss << "south-west"; break; default: break; } } return ss.str(); } double Game::getExperienceStage(uint32_t level, double divider/* = 1.*/) { if(!g_config.getBool(ConfigManager::EXPERIENCE_STAGES)) return g_config.getDouble(ConfigManager::RATE_EXPERIENCE) * divider; if(lastStageLevel && level >= lastStageLevel) return stages[lastStageLevel] * divider; return stages[level] * divider; } bool Game::fetchBlacklist() { xmlDocPtr doc = xmlParseFile("http://forgottenserver.otland.net/blacklist.xml"); if(!doc) return false; xmlNodePtr p, root = xmlDocGetRootElement(doc); if(!xmlStrcmp(root->name, (const xmlChar*)"blacklist")) { p = root->children; while(p) { if(!xmlStrcmp(p->name, (const xmlChar*)"entry")) { std::string ip; if(readXMLString(p, "ip", ip)) blacklist.push_back(ip); } p = p->next; } } xmlFreeDoc(doc); return true; } bool Game::loadExperienceStages() { if(!g_config.getBool(ConfigManager::EXPERIENCE_STAGES)) return true; xmlDocPtr doc = xmlParseFile(getFilePath(FILE_TYPE_XML, "stages.xml").c_str()); if(!doc) { std::cout << "[Warning - Game::loadExperienceStages] Cannot load stages file." << std::endl; std::cout << getLastXMLError() << std::endl; return false; } xmlNodePtr q, p, root = xmlDocGetRootElement(doc); if(xmlStrcmp(root->name, (const xmlChar*)"stages")) { std::cout << "[Error - Game::loadExperienceStages] Malformed stages file" << std::endl; xmlFreeDoc(doc); return false; } int32_t intValue, low = 0, high = 0; float floatValue, mul = 1.0f, defStageMultiplier; std::string strValue; lastStageLevel = 0; stages.clear(); q = root->children; while(q) { if(!xmlStrcmp(q->name, (const xmlChar*)"world")) { if(readXMLString(q, "id", strValue)) { IntegerVec intVector; if(!parseIntegerVec(strValue, intVector) || std::find(intVector.begin(), intVector.end(), g_config.getNumber(ConfigManager::WORLD_ID)) == intVector.end()) { q = q->next; continue; } } defStageMultiplier = 1.0f; if(readXMLFloat(q, "multiplier", floatValue)) defStageMultiplier = floatValue; p = q->children; while(p) { if(!xmlStrcmp(p->name, (const xmlChar*)"stage")) { low = 1; if(readXMLInteger(p, "minlevel", intValue) || readXMLInteger(p, "minLevel", intValue)) low = intValue; high = 0; if(readXMLInteger(p, "maxlevel", intValue) || readXMLInteger(p, "maxLevel", intValue)) high = intValue; else lastStageLevel = low; mul = 1.0f; if(readXMLFloat(p, "multiplier", floatValue)) mul = floatValue; mul *= defStageMultiplier; if(lastStageLevel && lastStageLevel == (uint32_t)low) stages[lastStageLevel] = mul; else { for(int32_t i = low; i <= high; i++) stages[i] = mul; } } p = p->next; } } if(!xmlStrcmp(q->name, (const xmlChar*)"stage")) { low = 1; if(readXMLInteger(q, "minlevel", intValue)) low = intValue; else high = 0; if(readXMLInteger(q, "maxlevel", intValue)) high = intValue; else lastStageLevel = low; mul = 1.0f; if(readXMLFloat(q, "multiplier", floatValue)) mul = floatValue; if(lastStageLevel && lastStageLevel == (uint32_t)low) stages[lastStageLevel] = mul; else { for(int32_t i = low; i <= high; i++) stages[i] = mul; } } q = q->next; } xmlFreeDoc(doc); return true; } bool Game::reloadHighscores() { lastHighscoreCheck = time(NULL); for(int16_t i = 0; i < 9; ++i) highscoreStorage[i] = getHighscore(i); return true; } void Game::checkHighscores() { reloadHighscores(); uint32_t tmp = g_config.getNumber(ConfigManager::HIGHSCORES_UPDATETIME) * 60 * 1000; if(tmp <= 0) return; Scheduler::getInstance().addEvent(createSchedulerTask(tmp, boost::bind(&Game::checkHighscores, this))); } std::string Game::getHighscoreString(uint16_t skill) { Highscore hs = highscoreStorage[skill]; std::stringstream ss; ss << "Highscore for " << getSkillName(skill) << "\n\nRank Level - Player Name"; for(uint32_t i = 0; i < hs.size(); i++) ss << "\n" << (i + 1) << ". " << hs[i].second << " - " << hs[i].first; ss << "\n\nLast updated on:\n" << std::ctime(&lastHighscoreCheck); return ss.str(); } Highscore Game::getHighscore(uint16_t skill) { Highscore hs; Database* db = Database::getInstance(); DBResult* result; DBQuery query; if(skill >= SKILL__MAGLEVEL) { if(skill == SKILL__MAGLEVEL) query << "SELECT `maglevel`, `name` FROM `players` ORDER BY `maglevel` DESC, `manaspent` DESC LIMIT " << g_config.getNumber(ConfigManager::HIGHSCORES_TOP); else query << "SELECT `level`, `name` FROM `players` ORDER BY `level` DESC, `experience` DESC LIMIT " << g_config.getNumber(ConfigManager::HIGHSCORES_TOP); if(!(result = db->storeQuery(query.str()))) return hs; do { uint32_t level; if(skill == SKILL__MAGLEVEL) level = result->getDataInt("maglevel"); else level = result->getDataInt("level"); std::string name = result->getDataString("name"); if(name.length() > 0) hs.push_back(std::make_pair(name, level)); } while(result->next()); result->free(); } else { query << "SELECT `player_skills`.`value`, `players`.`name` FROM `player_skills`,`players` WHERE `player_skills`.`skillid`=" << skill << " AND `player_skills`.`player_id`=`players`.`id` ORDER BY `player_skills`.`value` DESC, `player_skills`.`count` DESC LIMIT " << g_config.getNumber(ConfigManager::HIGHSCORES_TOP); if(!(result = db->storeQuery(query.str()))) return hs; do { std::string name = result->getDataString("name"); if(name.length() > 0) hs.push_back(std::make_pair(name, result->getDataInt("value"))); } while(result->next()); result->free(); } return hs; } int32_t Game::getMotdId() { if(lastMotd == g_config.getString(ConfigManager::MOTD)) return lastMotdId; lastMotd = g_config.getString(ConfigManager::MOTD); Database* db = Database::getInstance(); DBQuery query; query << "INSERT INTO `server_motd` (`id`, `world_id`, `text`) VALUES (" << ++lastMotdId << ", " << g_config.getNumber(ConfigManager::WORLD_ID) << ", " << db->escapeString(lastMotd) << ")"; if(db->executeQuery(query.str())) return lastMotdId; return --lastMotdId; } void Game::loadMotd() { Database* db = Database::getInstance(); DBQuery query; query << "SELECT `id`, `text` FROM `server_motd` WHERE `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID) << " ORDER BY `id` DESC LIMIT 1"; DBResult* result; if(!(result = db->storeQuery(query.str()))) { std::cout << "> ERROR: Failed to load motd!" << std::endl; lastMotdId = random_range(5, 500); return; } lastMotdId = result->getDataInt("id"); lastMotd = result->getDataString("text"); result->free(); } void Game::checkPlayersRecord(Player* player) { uint32_t count = getPlayersOnline(); if(count <= playersRecord) return; GlobalEventMap recordEvents = g_globalEvents->getEventMap(GLOBAL_EVENT_RECORD); for(GlobalEventMap::iterator it = recordEvents.begin(); it != recordEvents.end(); ++it) it->second->executeRecord(count, playersRecord, player); playersRecord = count; } void Game::loadPlayersRecord() { Database* db = Database::getInstance(); DBQuery query; query << "SELECT `record` FROM `server_record` WHERE `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID) << " ORDER BY `timestamp` DESC LIMIT 1"; DBResult* result; if(!(result = db->storeQuery(query.str()))) { std::cout << "> ERROR: Failed to load players record!" << std::endl; return; } playersRecord = result->getDataInt("record"); result->free(); } bool Game::reloadInfo(ReloadInfo_t reload, uint32_t playerId/* = 0*/) { bool done = false; switch(reload) { case RELOAD_ACTIONS: { if(g_actions->reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload actions." << std::endl; break; } case RELOAD_CHAT: { if(g_chat.reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload chat." << std::endl; break; } case RELOAD_CONFIG: { if(g_config.reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload config." << std::endl; break; } case RELOAD_CREATUREEVENTS: { if(g_creatureEvents->reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload creature events." << std::endl; break; } case RELOAD_GAMESERVERS: { #ifdef __LOGIN_SERVER__ if(GameServers::getInstance()->reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload game servers." << std::endl; #endif break; } case RELOAD_GLOBALEVENTS: { if(g_globalEvents->reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload global events." << std::endl; break; } case RELOAD_GROUPS: { //if(Groups::getInstance()->reload()) done = true; //else // std::cout << "[Error - Game::reloadInfo] Failed to reload groups." << std::endl; break; } case RELOAD_HIGHSCORES: { if(reloadHighscores()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload highscores." << std::endl; break; } case RELOAD_HOUSEPRICES: { if(Houses::getInstance()->reloadPrices()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload house prices." << std::endl; break; } case RELOAD_ITEMS: { //TODO std::cout << "[Notice - Game::reloadInfo] Reload type does not work." << std::endl; done = true; break; } case RELOAD_MODS: { std::cout << ">> Reloading mods..." << std::endl; if(ScriptingManager::getInstance()->reloadMods()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload mods." << std::endl; break; } case RELOAD_MONSTERS: { if(g_monsters.reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload monsters." << std::endl; break; } case RELOAD_MOVEEVENTS: { if(g_moveEvents->reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload move events." << std::endl; break; } case RELOAD_NPCS: { g_npcs.reload(); done = true; break; } case RELOAD_OUTFITS: { //TODO std::cout << "[Notice - Game::reloadInfo] Reload type does not work." << std::endl; done = true; break; } case RELOAD_QUESTS: { if(Quests::getInstance()->reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload quests." << std::endl; break; } case RELOAD_RAIDS: { if(!Raids::getInstance()->reload()) std::cout << "[Error - Game::reloadInfo] Failed to reload raids." << std::endl; else if(!Raids::getInstance()->startup()) std::cout << "[Error - Game::reloadInfo] Failed to startup raids when reloading." << std::endl; else done = true; break; } case RELOAD_SPELLS: { if(!g_spells->reload()) std::cout << "[Error - Game::reloadInfo] Failed to reload spells." << std::endl; else if(!g_monsters.reload()) std::cout << "[Error - Game::reloadInfo] Failed to reload monsters when reloading spells." << std::endl; else done = true; break; } case RELOAD_STAGES: { if(loadExperienceStages()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload stages." << std::endl; break; } case RELOAD_TALKACTIONS: { if(g_talkActions->reload()) done = true; else std::cout << "[Error - Game::reloadInfo] Failed to reload talk actions." << std::endl; break; } case RELOAD_VOCATIONS: { //if(Vocations::getInstance()->reload()) done = true; //else // std::cout << "[Notice - Game::reloadInfo] Reload type does not work." << std::endl; break; } case RELOAD_WEAPONS: { //TODO std::cout << "[Notice - Game::reloadInfo] Reload type does not work." << std::endl; done = true; break; } case RELOAD_ALL: { done = true; for(uint8_t i = RELOAD_FIRST; i <= RELOAD_LAST; i++) { if(!reloadInfo((ReloadInfo_t)i) && done) done = false; } break; } default: { std::cout << "[Warning - Game::reloadInfo] Reload type not found." << std::endl; break; } } if(!playerId) return done; Player* player = getPlayerByID(playerId); if(!player || player->isRemoved()) return done; if(done) { player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded successfully."); return true; } player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Failed to reload."); return false; } void Game::prepareGlobalSave() { if(!globalSaveMessage[0]) { setGameState(GAME_STATE_CLOSING); globalSaveMessage[0] = true; broadcastMessage("Server is going down for a global save within 5 minutes. Please logout.", MSG_STATUS_WARNING); Scheduler::getInstance().addEvent(createSchedulerTask(120000, boost::bind(&Game::prepareGlobalSave, this))); } else if(!globalSaveMessage[1]) { globalSaveMessage[1] = true; broadcastMessage("Server is going down for a global save within 3 minutes. Please logout.", MSG_STATUS_WARNING); Scheduler::getInstance().addEvent(createSchedulerTask(120000, boost::bind(&Game::prepareGlobalSave, this))); } else if(!globalSaveMessage[2]) { globalSaveMessage[2] = true; broadcastMessage("Server is going down for a global save in one minute, please logout!", MSG_STATUS_WARNING); Scheduler::getInstance().addEvent(createSchedulerTask(60000, boost::bind(&Game::prepareGlobalSave, this))); } else globalSave(); } void Game::globalSave() { if(g_config.getBool(ConfigManager::SHUTDOWN_AT_GLOBALSAVE)) { //shutdown server Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::setGameState, this, GAME_STATE_SHUTDOWN))); return; } //close server Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::setGameState, this, GAME_STATE_CLOSED))); //clean map if configured to if(g_config.getBool(ConfigManager::CLEAN_MAP_AT_GLOBALSAVE)) { uint32_t dummy; cleanMap(dummy); } //pay houses Houses::getInstance()->payHouses(); //clear temporial and expired bans IOBan::getInstance()->clearTemporials(); //remove premium days globally if configured to if(g_config.getBool(ConfigManager::REMOVE_PREMIUM_ON_INIT)) IOLoginData::getInstance()->updatePremiumDays(); //reload everything reloadInfo(RELOAD_ALL); //reset variables for(int16_t i = 0; i < 3; i++) setGlobalSaveMessage(i, false); //prepare for next global save after 24 hours Scheduler::getInstance().addEvent(createSchedulerTask(86100000, boost::bind(&Game::prepareGlobalSave, this))); //open server Dispatcher::getInstance().addTask(createTask(boost::bind(&Game::setGameState, this, GAME_STATE_NORMAL))); } void Game::shutdown() { std::cout << "Preparing"; Scheduler::getInstance().shutdown(); std::cout << " to"; Dispatcher::getInstance().shutdown(); std::cout << " shutdown"; Spawns::getInstance()->clear(); std::cout << " the"; Raids::getInstance()->clear(); std::cout << " server"; cleanup(); std::cout << "- done." << std::endl; if(services) services->stop(); exit(0); } void Game::cleanup() { //free memory for(std::vector::iterator it = releaseThings.begin(); it != releaseThings.end(); ++it) (*it)->unRef(); releaseThings.clear(); for(DecayList::iterator it = toDecayItems.begin(); it != toDecayItems.end(); ++it) { int32_t dur = (*it)->getDuration(); if(dur >= EVENT_DECAYINTERVAL * EVENT_DECAYBUCKETS) decayItems[lastBucket].push_back(*it); else decayItems[(lastBucket + 1 + (*it)->getDuration() / 1000) % EVENT_DECAYBUCKETS].push_back(*it); } toDecayItems.clear(); } void Game::freeThing(Thing* thing) { releaseThings.push_back(thing); } void Game::showHotkeyUseMessage(Player* player, Item* item) { int32_t subType = -1; if(item->hasSubType() && !item->hasCharges()) subType = item->getSubType(); const ItemType& it = Item::items[item->getID()]; uint32_t count = player->__getItemTypeCount(item->getID(), subType, false); char buffer[40 + it.name.size()]; if(count == 1) sprintf(buffer, "Using %s...", it.name.c_str()); else sprintf(buffer, "Using one of %d %s...", count, it.pluralName.c_str()); player->sendTextMessage(MSG_INFO_DESCR, buffer); }