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