Full Distribution

This commit is contained in:
rasanpedromujica
2019-01-16 17:16:38 -05:00
commit 009a571331
1258 changed files with 185603 additions and 0 deletions

71
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,71 @@
set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/otpch.cpp
${CMAKE_CURRENT_LIST_DIR}/actions.cpp
${CMAKE_CURRENT_LIST_DIR}/ban.cpp
${CMAKE_CURRENT_LIST_DIR}/baseevents.cpp
${CMAKE_CURRENT_LIST_DIR}/bed.cpp
${CMAKE_CURRENT_LIST_DIR}/behaviourdatabase.cpp
${CMAKE_CURRENT_LIST_DIR}/chat.cpp
${CMAKE_CURRENT_LIST_DIR}/combat.cpp
${CMAKE_CURRENT_LIST_DIR}/commands.cpp
${CMAKE_CURRENT_LIST_DIR}/condition.cpp
${CMAKE_CURRENT_LIST_DIR}/configmanager.cpp
${CMAKE_CURRENT_LIST_DIR}/connection.cpp
${CMAKE_CURRENT_LIST_DIR}/container.cpp
${CMAKE_CURRENT_LIST_DIR}/creature.cpp
${CMAKE_CURRENT_LIST_DIR}/creatureevent.cpp
${CMAKE_CURRENT_LIST_DIR}/cylinder.cpp
${CMAKE_CURRENT_LIST_DIR}/database.cpp
${CMAKE_CURRENT_LIST_DIR}/databasemanager.cpp
${CMAKE_CURRENT_LIST_DIR}/databasetasks.cpp
${CMAKE_CURRENT_LIST_DIR}/depotlocker.cpp
${CMAKE_CURRENT_LIST_DIR}/fileloader.cpp
${CMAKE_CURRENT_LIST_DIR}/game.cpp
${CMAKE_CURRENT_LIST_DIR}/globalevent.cpp
${CMAKE_CURRENT_LIST_DIR}/guild.cpp
${CMAKE_CURRENT_LIST_DIR}/groups.cpp
${CMAKE_CURRENT_LIST_DIR}/house.cpp
${CMAKE_CURRENT_LIST_DIR}/housetile.cpp
${CMAKE_CURRENT_LIST_DIR}/ioguild.cpp
${CMAKE_CURRENT_LIST_DIR}/iologindata.cpp
${CMAKE_CURRENT_LIST_DIR}/iomap.cpp
${CMAKE_CURRENT_LIST_DIR}/iomapserialize.cpp
${CMAKE_CURRENT_LIST_DIR}/item.cpp
${CMAKE_CURRENT_LIST_DIR}/items.cpp
${CMAKE_CURRENT_LIST_DIR}/luascript.cpp
${CMAKE_CURRENT_LIST_DIR}/mailbox.cpp
${CMAKE_CURRENT_LIST_DIR}/map.cpp
${CMAKE_CURRENT_LIST_DIR}/monster.cpp
${CMAKE_CURRENT_LIST_DIR}/monsters.cpp
${CMAKE_CURRENT_LIST_DIR}/movement.cpp
${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp
${CMAKE_CURRENT_LIST_DIR}/npc.cpp
${CMAKE_CURRENT_LIST_DIR}/otserv.cpp
${CMAKE_CURRENT_LIST_DIR}/outputmessage.cpp
${CMAKE_CURRENT_LIST_DIR}/party.cpp
${CMAKE_CURRENT_LIST_DIR}/player.cpp
${CMAKE_CURRENT_LIST_DIR}/position.cpp
${CMAKE_CURRENT_LIST_DIR}/protocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocolgame.cpp
${CMAKE_CURRENT_LIST_DIR}/protocollogin.cpp
${CMAKE_CURRENT_LIST_DIR}/protocolstatus.cpp
${CMAKE_CURRENT_LIST_DIR}/raids.cpp
${CMAKE_CURRENT_LIST_DIR}/rsa.cpp
${CMAKE_CURRENT_LIST_DIR}/scheduler.cpp
${CMAKE_CURRENT_LIST_DIR}/scriptmanager.cpp
${CMAKE_CURRENT_LIST_DIR}/server.cpp
${CMAKE_CURRENT_LIST_DIR}/spawn.cpp
${CMAKE_CURRENT_LIST_DIR}/spells.cpp
${CMAKE_CURRENT_LIST_DIR}/script.cpp
${CMAKE_CURRENT_LIST_DIR}/talkaction.cpp
${CMAKE_CURRENT_LIST_DIR}/tasks.cpp
${CMAKE_CURRENT_LIST_DIR}/teleport.cpp
${CMAKE_CURRENT_LIST_DIR}/thing.cpp
${CMAKE_CURRENT_LIST_DIR}/tile.cpp
${CMAKE_CURRENT_LIST_DIR}/tools.cpp
${CMAKE_CURRENT_LIST_DIR}/vocation.cpp
${CMAKE_CURRENT_LIST_DIR}/waitlist.cpp
${CMAKE_CURRENT_LIST_DIR}/weapons.cpp
${CMAKE_CURRENT_LIST_DIR}/wildcardtree.cpp
)

35
src/account.h Normal file
View File

@@ -0,0 +1,35 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_ACCOUNT_H_34817537BA2B4CB7B71AA562AFBB118F
#define FS_ACCOUNT_H_34817537BA2B4CB7B71AA562AFBB118F
#include "enums.h"
struct Account {
std::vector<std::string> characters;
time_t lastDay = 0;
uint32_t id = 0;
uint16_t premiumDays = 0;
AccountType_t accountType = ACCOUNT_TYPE_NORMAL;
Account() = default;
};
#endif

425
src/actions.cpp Normal file
View File

@@ -0,0 +1,425 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "actions.h"
#include "bed.h"
#include "configmanager.h"
#include "container.h"
#include "game.h"
#include "pugicast.h"
#include "spells.h"
extern Game g_game;
extern Spells* g_spells;
extern Actions* g_actions;
extern ConfigManager g_config;
Actions::Actions() :
scriptInterface("Action Interface")
{
scriptInterface.initState();
}
Actions::~Actions()
{
clear();
}
inline void Actions::clearMap(ActionUseMap& map)
{
// Filter out duplicates to avoid double-free
std::unordered_set<Action*> set;
for (const auto& it : map) {
set.insert(it.second);
}
map.clear();
for (Action* action : set) {
delete action;
}
}
void Actions::clear()
{
clearMap(useItemMap);
clearMap(actionItemMap);
scriptInterface.reInitState();
}
LuaScriptInterface& Actions::getScriptInterface()
{
return scriptInterface;
}
std::string Actions::getScriptBaseName() const
{
return "actions";
}
Event* Actions::getEvent(const std::string& nodeName)
{
if (strcasecmp(nodeName.c_str(), "action") != 0) {
return nullptr;
}
return new Action(&scriptInterface);
}
bool Actions::registerEvent(Event* event, const pugi::xml_node& node)
{
Action* action = static_cast<Action*>(event); //event is guaranteed to be an Action
pugi::xml_attribute attr;
if ((attr = node.attribute("itemid"))) {
uint16_t id = pugi::cast<uint16_t>(attr.value());
auto result = useItemMap.emplace(id, action);
if (!result.second) {
std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << std::endl;
}
return result.second;
} else if ((attr = node.attribute("fromid"))) {
pugi::xml_attribute toIdAttribute = node.attribute("toid");
if (!toIdAttribute) {
std::cout << "[Warning - Actions::registerEvent] Missing toid in fromid: " << attr.as_string() << std::endl;
return false;
}
uint16_t fromId = pugi::cast<uint16_t>(attr.value());
uint16_t iterId = fromId;
uint16_t toId = pugi::cast<uint16_t>(toIdAttribute.value());
auto result = useItemMap.emplace(iterId, action);
if (!result.second) {
std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl;
}
bool success = result.second;
while (++iterId <= toId) {
result = useItemMap.emplace(iterId, action);
if (!result.second) {
std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl;
continue;
}
success = true;
}
return success;
} else if ((attr = node.attribute("actionid"))) {
uint16_t aid = pugi::cast<uint16_t>(attr.value());
auto result = actionItemMap.emplace(aid, action);
if (!result.second) {
std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << aid << std::endl;
}
return result.second;
} else if ((attr = node.attribute("fromaid"))) {
pugi::xml_attribute toAidAttribute = node.attribute("toaid");
if (!toAidAttribute) {
std::cout << "[Warning - Actions::registerEvent] Missing toaid in fromaid: " << attr.as_string() << std::endl;
return false;
}
uint16_t fromAid = pugi::cast<uint16_t>(attr.value());
uint16_t iterAid = fromAid;
uint16_t toAid = pugi::cast<uint16_t>(toAidAttribute.value());
auto result = actionItemMap.emplace(iterAid, action);
if (!result.second) {
std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl;
}
bool success = result.second;
while (++iterAid <= toAid) {
result = actionItemMap.emplace(iterAid, action);
if (!result.second) {
std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl;
continue;
}
success = true;
}
return success;
}
return false;
}
ReturnValue Actions::canUse(const Player* player, const Position& pos)
{
if (pos.x != 0xFFFF) {
const Position& playerPos = player->getPosition();
if (playerPos.z != pos.z) {
return playerPos.z > pos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS;
}
if (!Position::areInRange<1, 1>(playerPos, pos)) {
return RETURNVALUE_TOOFARAWAY;
}
}
return RETURNVALUE_NOERROR;
}
ReturnValue Actions::canUse(const Player* player, const Position& pos, const Item* item)
{
Action* action = getAction(item);
if (action) {
return action->canExecuteAction(player, pos);
}
return RETURNVALUE_NOERROR;
}
ReturnValue Actions::canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor)
{
if (toPos.x == 0xFFFF) {
return RETURNVALUE_NOERROR;
}
const Position& creaturePos = creature->getPosition();
if (checkFloor && creaturePos.z != toPos.z) {
return creaturePos.z > toPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS;
}
if (!Position::areInRange<7, 5>(toPos, creaturePos)) {
return RETURNVALUE_TOOFARAWAY;
}
if (checkLineOfSight && !g_game.canThrowObjectTo(creaturePos, toPos)) {
return RETURNVALUE_CANNOTTHROW;
}
return RETURNVALUE_NOERROR;
}
Action* Actions::getAction(const Item* item)
{
if (item->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) {
auto it = actionItemMap.find(item->getActionId());
if (it != actionItemMap.end()) {
return it->second;
}
}
auto it = useItemMap.find(item->getID());
if (it != useItemMap.end()) {
return it->second;
}
//rune items
return g_spells->getRuneSpell(item->getID());
}
ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item)
{
if (Door* door = item->getDoor()) {
if (!door->canUse(player)) {
return RETURNVALUE_CANNOTUSETHISOBJECT;
}
}
Action* action = getAction(item);
if (action) {
if (action->isScripted()) {
if (action->executeUse(player, item, pos, nullptr, pos)) {
return RETURNVALUE_NOERROR;
}
if (item->isRemoved()) {
return RETURNVALUE_CANNOTUSETHISOBJECT;
}
}
}
if (BedItem* bed = item->getBed()) {
if (!bed->canUse(player)) {
return RETURNVALUE_CANNOTUSETHISOBJECT;
}
if (bed->trySleep(player)) {
player->setBedItem(bed);
if (!bed->sleep(player)) {
return RETURNVALUE_CANNOTUSETHISOBJECT;
}
}
return RETURNVALUE_NOERROR;
}
if (Container* container = item->getContainer()) {
if (!item->isChestQuest()) {
Container* openContainer;
//depot container
if (DepotLocker* depot = container->getDepotLocker()) {
DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId(), true);
myDepotLocker->setParent(depot->getParent()->getTile());
openContainer = myDepotLocker;
} else {
openContainer = container;
}
//open/close container
int32_t oldContainerId = player->getContainerID(openContainer);
if (oldContainerId != -1) {
player->onCloseContainer(openContainer);
player->closeContainer(oldContainerId);
} else {
player->addContainer(index, openContainer);
player->onSendContainer(openContainer);
}
return RETURNVALUE_NOERROR;
}
}
const ItemType& it = Item::items[item->getID()];
if (it.canReadText) {
if (it.canWriteText) {
player->setWriteItem(item, it.maxTextLen);
player->sendTextWindow(item, it.maxTextLen, true);
} else {
player->setWriteItem(nullptr);
player->sendTextWindow(item, 0, false);
}
return RETURNVALUE_NOERROR;
} else if (it.changeUse) {
if (it.transformToOnUse) {
g_game.transformItem(item, it.transformToOnUse);
g_game.startDecay(item);
return RETURNVALUE_NOERROR;
}
}
return RETURNVALUE_CANNOTUSETHISOBJECT;
}
bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item)
{
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL));
player->stopWalk();
ReturnValue ret = internalUseItem(player, pos, index, item);
if (ret != RETURNVALUE_NOERROR) {
player->sendCancelMessage(ret);
return false;
}
return true;
}
bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& toPos,
uint8_t toStackPos, Item* item, Creature* creature/* = nullptr*/)
{
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL));
player->stopWalk();
Action* action = getAction(item);
if (!action) {
player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
return false;
}
ReturnValue ret = action->canExecuteAction(player, toPos);
if (ret != RETURNVALUE_NOERROR) {
player->sendCancelMessage(ret);
return false;
}
if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos)) {
if (!action->hasOwnErrorHandler()) {
player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
}
return false;
}
return true;
}
Action::Action(LuaScriptInterface* interface) :
Event(interface), allowFarUse(false), checkFloor(true), checkLineOfSight(true) {}
Action::Action(const Action* copy) :
Event(copy), allowFarUse(copy->allowFarUse), checkFloor(copy->checkFloor), checkLineOfSight(copy->checkLineOfSight) {}
bool Action::configureEvent(const pugi::xml_node& node)
{
pugi::xml_attribute allowFarUseAttr = node.attribute("allowfaruse");
if (allowFarUseAttr) {
setAllowFarUse(allowFarUseAttr.as_bool());
}
pugi::xml_attribute blockWallsAttr = node.attribute("blockwalls");
if (blockWallsAttr) {
setCheckLineOfSight(blockWallsAttr.as_bool());
}
pugi::xml_attribute checkFloorAttr = node.attribute("checkfloor");
if (checkFloorAttr) {
setCheckFloor(checkFloorAttr.as_bool());
}
return true;
}
std::string Action::getScriptEventName() const
{
return "onUse";
}
ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos)
{
if (!getAllowFarUse()) {
return g_actions->canUse(player, toPos);
} else {
return g_actions->canUseFar(player, toPos, getCheckLineOfSight(), getCheckFloor());
}
}
Thing* Action::getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const
{
if (targetCreature) {
return targetCreature;
}
return g_game.internalGetThing(player, toPosition, toStackPos, 0, STACKPOS_USETARGET);
}
bool Action::executeUse(Player* player, Item* item, const Position& fromPos, Thing* target, const Position& toPos)
{
//onUse(player, item, fromPosition, target, toPosition)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - Action::executeUse] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata<Player>(L, player);
LuaScriptInterface::setMetatable(L, -1, "Player");
LuaScriptInterface::pushThing(L, item);
LuaScriptInterface::pushPosition(L, fromPos);
LuaScriptInterface::pushThing(L, target);
LuaScriptInterface::pushPosition(L, toPos);
return scriptInterface->callFunction(5);
}

111
src/actions.h Normal file
View File

@@ -0,0 +1,111 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_ACTIONS_H_87F60C5F587E4B84948F304A6451E6E6
#define FS_ACTIONS_H_87F60C5F587E4B84948F304A6451E6E6
#include "baseevents.h"
#include "enums.h"
#include "luascript.h"
class Action : public Event
{
public:
explicit Action(const Action* copy);
explicit Action(LuaScriptInterface* interface);
bool configureEvent(const pugi::xml_node& node) override;
//scripting
virtual bool executeUse(Player* player, Item* item, const Position& fromPosition,
Thing* target, const Position& toPosition);
//
bool getAllowFarUse() const {
return allowFarUse;
}
void setAllowFarUse(bool v) {
allowFarUse = v;
}
bool getCheckLineOfSight() const {
return checkLineOfSight;
}
void setCheckLineOfSight(bool v) {
checkLineOfSight = v;
}
bool getCheckFloor() const {
return checkFloor;
}
void setCheckFloor(bool v) {
checkFloor = v;
}
virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos);
virtual bool hasOwnErrorHandler() {
return false;
}
virtual Thing* getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const;
protected:
std::string getScriptEventName() const override;
bool allowFarUse;
bool checkFloor;
bool checkLineOfSight;
};
class Actions final : public BaseEvents
{
public:
Actions();
~Actions();
// non-copyable
Actions(const Actions&) = delete;
Actions& operator=(const Actions&) = delete;
bool useItem(Player* player, const Position& pos, uint8_t index, Item* item);
bool useItemEx(Player* player, const Position& fromPos, const Position& toPos, uint8_t toStackPos, Item* item, Creature* creature = nullptr);
ReturnValue canUse(const Player* player, const Position& pos);
ReturnValue canUse(const Player* player, const Position& pos, const Item* item);
ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor);
protected:
ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item);
void clear() final;
LuaScriptInterface& getScriptInterface() final;
std::string getScriptBaseName() const final;
Event* getEvent(const std::string& nodeName) final;
bool registerEvent(Event* event, const pugi::xml_node& node) final;
typedef std::map<uint16_t, Action*> ActionUseMap;
ActionUseMap useItemMap;
ActionUseMap actionItemMap;
Action* getAction(const Item* item);
void clearMap(ActionUseMap& map);
LuaScriptInterface scriptInterface;
};
#endif

127
src/ban.cpp Normal file
View File

@@ -0,0 +1,127 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "ban.h"
#include "database.h"
#include "databasetasks.h"
#include "tools.h"
bool Ban::acceptConnection(uint32_t clientip)
{
std::lock_guard<std::recursive_mutex> lockClass(lock);
uint64_t currentTime = OTSYS_TIME();
auto it = ipConnectMap.find(clientip);
if (it == ipConnectMap.end()) {
ipConnectMap.emplace(clientip, ConnectBlock(currentTime, 0, 1));
return true;
}
ConnectBlock& connectBlock = it->second;
if (connectBlock.blockTime > currentTime) {
connectBlock.blockTime += 250;
return false;
}
int64_t timeDiff = currentTime - connectBlock.lastAttempt;
connectBlock.lastAttempt = currentTime;
if (timeDiff <= 5000) {
if (++connectBlock.count > 5) {
connectBlock.count = 0;
if (timeDiff <= 500) {
connectBlock.blockTime = currentTime + 3000;
return false;
}
}
} else {
connectBlock.count = 1;
}
return true;
}
bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `reason`, `expires_at`, `banned_at`, `banned_by`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `account_bans` WHERE `account_id` = " << accountId;
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return false;
}
int64_t expiresAt = result->getNumber<int64_t>("expires_at");
if (expiresAt != 0 && time(nullptr) > expiresAt) {
// Move the ban to history if it has expired
query.str(std::string());
query << "INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" << accountId << ',' << db->escapeString(result->getString("reason")) << ',' << result->getNumber<time_t>("banned_at") << ',' << expiresAt << ',' << result->getNumber<uint32_t>("banned_by") << ')';
g_databaseTasks.addTask(query.str());
query.str(std::string());
query << "DELETE FROM `account_bans` WHERE `account_id` = " << accountId;
g_databaseTasks.addTask(query.str());
return false;
}
banInfo.expiresAt = expiresAt;
banInfo.reason = result->getString("reason");
banInfo.bannedBy = result->getString("name");
return true;
}
bool IOBan::isIpBanned(uint32_t clientip, BanInfo& banInfo)
{
if (clientip == 0) {
return false;
}
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `reason`, `expires_at`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `ip_bans` WHERE `ip` = " << clientip;
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return false;
}
int64_t expiresAt = result->getNumber<int64_t>("expires_at");
if (expiresAt != 0 && time(nullptr) > expiresAt) {
query.str(std::string());
query << "DELETE FROM `ip_bans` WHERE `ip` = " << clientip;
g_databaseTasks.addTask(query.str());
return false;
}
banInfo.expiresAt = expiresAt;
banInfo.reason = result->getString("reason");
banInfo.bannedBy = result->getString("name");
return true;
}
bool IOBan::isPlayerNamelocked(uint32_t playerId)
{
std::ostringstream query;
query << "SELECT 1 FROM `player_namelocks` WHERE `player_id` = " << playerId;
return Database::getInstance()->storeQuery(query.str()).get() != nullptr;
}

58
src/ban.h Normal file
View File

@@ -0,0 +1,58 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_BAN_H_CADB975222D745F0BDA12D982F1006E3
#define FS_BAN_H_CADB975222D745F0BDA12D982F1006E3
struct BanInfo {
std::string bannedBy;
std::string reason;
time_t expiresAt;
};
struct ConnectBlock {
constexpr ConnectBlock(uint64_t lastAttempt, uint64_t blockTime, uint32_t count) :
lastAttempt(lastAttempt), blockTime(blockTime), count(count) {}
uint64_t lastAttempt;
uint64_t blockTime;
uint32_t count;
};
typedef std::map<uint32_t, ConnectBlock> IpConnectMap;
class Ban
{
public:
bool acceptConnection(uint32_t clientip);
protected:
IpConnectMap ipConnectMap;
std::recursive_mutex lock;
};
class IOBan
{
public:
static bool isAccountBanned(uint32_t accountId, BanInfo& banInfo);
static bool isIpBanned(uint32_t ip, BanInfo& banInfo);
static bool isPlayerNamelocked(uint32_t playerId);
};
#endif

165
src/baseevents.cpp Normal file
View File

@@ -0,0 +1,165 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "baseevents.h"
#include "pugicast.h"
#include "tools.h"
extern LuaEnvironment g_luaEnvironment;
bool BaseEvents::loadFromXml()
{
if (loaded) {
std::cout << "[Error - BaseEvents::loadFromXml] It's already loaded." << std::endl;
return false;
}
std::string scriptsName = getScriptBaseName();
std::string basePath = "data/" + scriptsName + "/";
if (getScriptInterface().loadFile(basePath + "lib/" + scriptsName + ".lua") == -1) {
std::cout << "[Warning - BaseEvents::loadFromXml] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl;
}
std::string filename = basePath + scriptsName + ".xml";
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(filename.c_str());
if (!result) {
printXMLError("Error - BaseEvents::loadFromXml", filename, result);
return false;
}
loaded = true;
for (auto node : doc.child(scriptsName.c_str()).children()) {
Event* event = getEvent(node.name());
if (!event) {
continue;
}
if (!event->configureEvent(node)) {
std::cout << "[Warning - BaseEvents::loadFromXml] Failed to configure event" << std::endl;
delete event;
continue;
}
bool success;
pugi::xml_attribute scriptAttribute = node.attribute("script");
if (scriptAttribute) {
std::string scriptFile = "scripts/" + std::string(scriptAttribute.as_string());
success = event->checkScript(basePath, scriptsName, scriptFile) && event->loadScript(basePath + scriptFile);
} else {
success = event->loadFunction(node.attribute("function"));
}
if (!success || !registerEvent(event, node)) {
delete event;
}
}
return true;
}
bool BaseEvents::reload()
{
loaded = false;
clear();
return loadFromXml();
}
Event::Event(LuaScriptInterface* interface) : scriptInterface(interface) {}
Event::Event(const Event* copy) :
scripted(copy->scripted), scriptId(copy->scriptId), scriptInterface(copy->scriptInterface) {}
bool Event::checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const
{
LuaScriptInterface* testInterface = g_luaEnvironment.getTestInterface();
testInterface->reInitState();
if (testInterface->loadFile(std::string(basePath + "lib/" + scriptsName + ".lua")) == -1) {
std::cout << "[Warning - Event::checkScript] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl;
}
if (scriptId != 0) {
std::cout << "[Failure - Event::checkScript] scriptid = " << scriptId << std::endl;
return false;
}
if (testInterface->loadFile(basePath + scriptFile) == -1) {
std::cout << "[Warning - Event::checkScript] Can not load script: " << scriptFile << std::endl;
std::cout << testInterface->getLastLuaError() << std::endl;
return false;
}
int32_t id = testInterface->getEvent(getScriptEventName());
if (id == -1) {
std::cout << "[Warning - Event::checkScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl;
return false;
}
return true;
}
bool Event::loadScript(const std::string& scriptFile)
{
if (!scriptInterface || scriptId != 0) {
std::cout << "Failure: [Event::loadScript] scriptInterface == nullptr. scriptid = " << scriptId << std::endl;
return false;
}
if (scriptInterface->loadFile(scriptFile) == -1) {
std::cout << "[Warning - Event::loadScript] Can not load script. " << scriptFile << std::endl;
std::cout << scriptInterface->getLastLuaError() << std::endl;
return false;
}
int32_t id = scriptInterface->getEvent(getScriptEventName());
if (id == -1) {
std::cout << "[Warning - Event::loadScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl;
return false;
}
scripted = true;
scriptId = id;
return true;
}
bool CallBack::loadCallBack(LuaScriptInterface* interface, const std::string& name)
{
if (!interface) {
std::cout << "Failure: [CallBack::loadCallBack] scriptInterface == nullptr" << std::endl;
return false;
}
scriptInterface = interface;
int32_t id = scriptInterface->getEvent(name.c_str());
if (id == -1) {
std::cout << "[Warning - CallBack::loadCallBack] Event " << name << " not found." << std::endl;
return false;
}
callbackName = name;
scriptId = id;
loaded = true;
return true;
}

90
src/baseevents.h Normal file
View File

@@ -0,0 +1,90 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_BASEEVENTS_H_9994E32C91CE4D95912A5FDD1F41884A
#define FS_BASEEVENTS_H_9994E32C91CE4D95912A5FDD1F41884A
#include "luascript.h"
class Event
{
public:
explicit Event(LuaScriptInterface* interface);
explicit Event(const Event* copy);
virtual ~Event() = default;
virtual bool configureEvent(const pugi::xml_node& node) = 0;
bool checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const;
bool loadScript(const std::string& scriptFile);
virtual bool loadFunction(const pugi::xml_attribute&) {
return false;
}
bool isScripted() const {
return scripted;
}
protected:
virtual std::string getScriptEventName() const = 0;
bool scripted = false;
int32_t scriptId = 0;
LuaScriptInterface* scriptInterface = nullptr;
};
class BaseEvents
{
public:
constexpr BaseEvents() = default;
virtual ~BaseEvents() = default;
bool loadFromXml();
bool reload();
bool isLoaded() const {
return loaded;
}
protected:
virtual LuaScriptInterface& getScriptInterface() = 0;
virtual std::string getScriptBaseName() const = 0;
virtual Event* getEvent(const std::string& nodeName) = 0;
virtual bool registerEvent(Event* event, const pugi::xml_node& node) = 0;
virtual void clear() = 0;
bool loaded = false;
};
class CallBack
{
public:
CallBack() = default;
bool loadCallBack(LuaScriptInterface* interface, const std::string& name);
protected:
int32_t scriptId = 0;
LuaScriptInterface* scriptInterface = nullptr;
bool loaded = false;
std::string callbackName;
};
#endif

277
src/bed.cpp Normal file
View File

@@ -0,0 +1,277 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "bed.h"
#include "game.h"
#include "iologindata.h"
#include "scheduler.h"
extern Game g_game;
BedItem::BedItem(uint16_t id) : Item(id)
{
internalRemoveSleeper();
}
Attr_ReadValue BedItem::readAttr(AttrTypes_t attr, PropStream& propStream)
{
switch (attr) {
case ATTR_SLEEPERGUID: {
uint32_t guid;
if (!propStream.read<uint32_t>(guid)) {
return ATTR_READ_ERROR;
}
if (guid != 0) {
std::string name = IOLoginData::getNameByGuid(guid);
if (!name.empty()) {
setSpecialDescription(name + " is sleeping there.");
g_game.setBedSleeper(this, guid);
sleeperGUID = guid;
}
}
return ATTR_READ_CONTINUE;
}
case ATTR_SLEEPSTART: {
uint32_t sleep_start;
if (!propStream.read<uint32_t>(sleep_start)) {
return ATTR_READ_ERROR;
}
sleepStart = static_cast<uint64_t>(sleep_start);
return ATTR_READ_CONTINUE;
}
default:
break;
}
return Item::readAttr(attr, propStream);
}
void BedItem::serializeAttr(PropWriteStream& propWriteStream) const
{
if (sleeperGUID != 0) {
propWriteStream.write<uint8_t>(ATTR_SLEEPERGUID);
propWriteStream.write<uint32_t>(sleeperGUID);
}
if (sleepStart != 0) {
propWriteStream.write<uint8_t>(ATTR_SLEEPSTART);
// FIXME: should be stored as 64-bit, but we need to retain backwards compatibility
propWriteStream.write<uint32_t>(static_cast<uint32_t>(sleepStart));
}
}
BedItem* BedItem::getNextBedItem() const
{
Direction dir = Item::items[id].bedPartnerDir;
Position targetPos = getNextPosition(dir, getPosition());
Tile* tile = g_game.map.getTile(targetPos);
if (!tile) {
return nullptr;
}
return tile->getBedItem();
}
bool BedItem::canUse(Player* player)
{
if (!player || !house || !player->isPremium()) {
return false;
}
if (sleeperGUID == 0) {
return true;
}
if (house->getHouseAccessLevel(player) == HOUSE_OWNER) {
return true;
}
Player sleeper(nullptr);
if (!IOLoginData::loadPlayerById(&sleeper, sleeperGUID)) {
return false;
}
if (house->getHouseAccessLevel(&sleeper) > house->getHouseAccessLevel(player)) {
return false;
}
return true;
}
bool BedItem::trySleep(Player* player)
{
if (!house || player->isRemoved()) {
return false;
}
if (sleeperGUID != 0) {
if (Item::items[id].transformToFree != 0 && house->getOwner() == player->getGUID()) {
wakeUp(nullptr);
}
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
return false;
}
return true;
}
bool BedItem::sleep(Player* player)
{
if (!house) {
return false;
}
if (sleeperGUID != 0) {
return false;
}
BedItem* nextBedItem = getNextBedItem();
internalSetSleeper(player);
if (nextBedItem) {
nextBedItem->internalSetSleeper(player);
}
// update the bedSleepersMap
g_game.setBedSleeper(this, player->getGUID());
// make the player walk onto the bed
g_game.map.moveCreature(*player, *getTile());
// display poff effect
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
// kick player after he sees himself walk onto the bed and it change id
uint32_t playerId = player->getID();
g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&Game::kickPlayer, &g_game, playerId, false)));
// change self and partner's appearance
updateAppearance(player);
if (nextBedItem) {
nextBedItem->updateAppearance(player);
}
return true;
}
void BedItem::wakeUp(Player* player)
{
if (!house) {
return;
}
if (sleeperGUID != 0) {
if (!player) {
Player regenPlayer(nullptr);
if (IOLoginData::loadPlayerById(&regenPlayer, sleeperGUID)) {
regeneratePlayer(&regenPlayer);
IOLoginData::savePlayer(&regenPlayer);
}
} else {
regeneratePlayer(player);
g_game.addCreatureHealth(player);
}
}
// update the bedSleepersMap
g_game.removeBedSleeper(sleeperGUID);
BedItem* nextBedItem = getNextBedItem();
// unset sleep info
internalRemoveSleeper();
if (nextBedItem) {
nextBedItem->internalRemoveSleeper();
}
// change self and partner's appearance
updateAppearance(nullptr);
if (nextBedItem) {
nextBedItem->updateAppearance(nullptr);
}
}
void BedItem::regeneratePlayer(Player* player) const
{
const uint32_t sleptTime = time(nullptr) - sleepStart;
Condition* condition = player->getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT);
if (condition) {
uint32_t regen;
if (condition->getTicks() != -1) {
regen = std::min<int32_t>((condition->getTicks() / 1000), sleptTime) / 30;
const int32_t newRegenTicks = condition->getTicks() - (regen * 30000);
if (newRegenTicks <= 0) {
player->removeCondition(condition);
} else {
condition->setTicks(newRegenTicks);
}
} else {
regen = sleptTime / 30;
}
player->changeHealth(regen, false);
player->changeMana(regen);
}
const int32_t soulRegen = sleptTime / (60 * 15);
player->changeSoul(soulRegen);
}
void BedItem::updateAppearance(const Player* player)
{
const ItemType& it = Item::items[id];
if (it.type == ITEM_TYPE_BED) {
if (player && it.transformToOnUse != 0) {
const ItemType& newType = Item::items[it.transformToOnUse];
if (newType.type == ITEM_TYPE_BED) {
g_game.transformItem(this, it.transformToOnUse);
}
} else if (it.transformToFree != 0) {
const ItemType& newType = Item::items[it.transformToFree];
if (newType.type == ITEM_TYPE_BED) {
g_game.transformItem(this, it.transformToFree);
}
}
}
}
void BedItem::internalSetSleeper(const Player* player)
{
std::string desc_str = player->getName() + " is sleeping there.";
sleeperGUID = player->getGUID();
sleepStart = time(nullptr);
setSpecialDescription(desc_str);
}
void BedItem::internalRemoveSleeper()
{
sleeperGUID = 0;
sleepStart = 0;
setSpecialDescription("Nobody is sleeping there.");
}

77
src/bed.h Normal file
View File

@@ -0,0 +1,77 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_BED_H_84DE19758D424C6C9789189231946BFF
#define FS_BED_H_84DE19758D424C6C9789189231946BFF
#include "item.h"
class House;
class Player;
class BedItem final : public Item
{
public:
explicit BedItem(uint16_t id);
BedItem* getBed() final {
return this;
}
const BedItem* getBed() const final {
return this;
}
Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final;
void serializeAttr(PropWriteStream& propWriteStream) const final;
bool canRemove() const final {
return house == nullptr;
}
uint32_t getSleeper() const {
return sleeperGUID;
}
House* getHouse() const {
return house;
}
void setHouse(House* h) {
house = h;
}
bool canUse(Player* player);
bool trySleep(Player* player);
bool sleep(Player* player);
void wakeUp(Player* player);
BedItem* getNextBedItem() const;
protected:
void updateAppearance(const Player* player);
void regeneratePlayer(Player* player) const;
void internalSetSleeper(const Player* player);
void internalRemoveSleeper();
House* house = nullptr;
uint64_t sleepStart;
uint32_t sleeperGUID;
};
#endif

1301
src/behaviourdatabase.cpp Normal file

File diff suppressed because it is too large Load Diff

293
src/behaviourdatabase.h Normal file
View File

@@ -0,0 +1,293 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_BEHAVIOURDATABASE_H_4E785D4C545C455E1A0C2A9C84D5EFFF
#define FS_BEHAVIOURDATABASE_H_4E785D4C545C455E1A0C2A9C84D5EFFF
#include "script.h"
#include "enums.h"
enum BehaviourSituation_t
{
SITUATION_ADDRESS = 1,
SITUATION_BUSY,
SITUATION_VANISH,
SITUATION_NONE,
};
enum NpcBehaviourType_t
{
BEHAVIOUR_TYPE_NOP = 0, // returns true on conditions
BEHAVIOUR_TYPE_STRING, // match string, or NPC say
BEHAVIOUR_TYPE_NUMBER, // return a number
BEHAVIOUR_TYPE_OPERATION, // <, =, >, >=, <=, <>
BEHAVIOUR_TYPE_MESSAGE_COUNT, // get quantity in player message
BEHAVIOUR_TYPE_IDLE, // idle npc
BEHAVIOUR_TYPE_QUEUE, // queue talking creature
BEHAVIOUR_TYPE_TOPIC, // get/set topic
BEHAVIOUR_TYPE_PRICE, // get/set price
BEHAVIOUR_TYPE_DATA, // get/set data
BEHAVIOUR_TYPE_ITEM, // get/set item
BEHAVIOUR_TYPE_AMOUNT, // get/set amount
BEHAVIOUR_TYPE_TEXT, // get/set string
BEHAVIOUR_TYPE_HEALTH, // get/set health
BEHAVIOUR_TYPE_COUNT, // count amount of items
BEHAVIOUR_TYPE_CREATEMONEY, // create money
BEHAVIOUR_TYPE_COUNTMONEY, // get player total money
BEHAVIOUR_TYPE_DELETEMONEY, // remove money from player
BEHAVIOUR_TYPE_CREATE, // create item
BEHAVIOUR_TYPE_DELETE, // deletes an item
BEHAVIOUR_TYPE_EFFECTME, // effect on NPC
BEHAVIOUR_TYPE_EFFECTOPP, // effect on player
BEHAVIOUR_TYPE_BURNING, // get/set burning
BEHAVIOUR_TYPE_POISON, // get/set poison
BEHAVIOUR_TYPE_SPELLKNOWN, // check if spell is known
BEHAVIOUR_TYPE_SPELLLEVEL, // get spell level
BEHAVIOUR_TYPE_TEACHSPELL, // player learn spell
BEHAVIOUR_TYPE_LEVEL, // get player level
BEHAVIOUR_TYPE_RANDOM, // random value
BEHAVIOUR_TYPE_QUESTVALUE, // get/set quest value
BEHAVIOUR_TYPE_TELEPORT, // teleport player to position
BEHAVIOUR_TYPE_SORCERER, // get/set vocation
BEHAVIOUR_TYPE_DRUID, // get/set vocation
BEHAVIOUR_TYPE_KNIGHT, // get/set vocation
BEHAVIOUR_TYPE_PALADIN, // get/set vocation
BEHAVIOUR_TYPE_ISPREMIUM, // is account premium
BEHAVIOUR_TYPE_PVPENFORCED, // get world type pvpenforced
BEHAVIOUR_TYPE_MALE, // is player male
BEHAVIOUR_TYPE_FEMALE, // is player female
BEHAVIOUR_TYPE_PZLOCKED, // is player pz locked
BEHAVIOUR_TYPE_PROMOTED, // check if player promoted
BEHAVIOUR_TYPE_PROFESSION, // get/set profession
BEHAVIOUR_TYPE_PROMOTE, // promote the player
BEHAVIOUR_TYPE_SUMMON, // summons a monster
BEHAVIOUR_TYPE_EXPERIENCE, // grant experience to a player
BEHAVIOUR_TYPE_BALANCE, // return player balance
BEHAVIOUR_TYPE_WITHDRAW, // withdraw from player bank balance
BEHAVIOUR_TYPE_DEPOSIT, // deposit x amount of gold
BEHAVIOUR_TYPE_TRANSFER, // transfer x amount of gold
BEHAVIOUR_TYPE_BLESS, // add blessing to player
BEHAVIOUR_TYPE_CREATECONTAINER, // create a container of an item in particular
BEHAVIOUR_TYPE_TOWN, // change player town
};
enum NpcBehaviourOperator_t
{
BEHAVIOUR_OPERATOR_LESSER_THAN = '<',
BEHAVIOUR_OPERATOR_EQUALS = '=',
BEHAVIOUR_OPERATOR_GREATER_THAN = '>',
BEHAVIOUR_OPERATOR_GREATER_OR_EQUALS = 'G',
BEHAVIOUR_OPERATOR_LESSER_OR_EQUALS = 'L',
BEHAVIOUR_OPERATOR_NOT_EQUALS = 'N',
BEHAVIOUR_OPERATOR_MULTIPLY = '*',
BEHAVIOUR_OPERATOR_SUM = '+',
BEHAVIOUR_OPERATOR_RES = '-',
};
enum NpcBehaviourParameterSearch_t
{
BEHAVIOUR_PARAMETER_NONE,
BEHAVIOUR_PARAMETER_ASSIGN,
BEHAVIOUR_PARAMETER_ONE,
BEHAVIOUR_PARAMETER_TWO,
BEHAVIOUR_PARAMETER_THREE,
};
class Npc;
class Player;
struct NpcBehaviourNode
{
NpcBehaviourType_t type;
int32_t number;
std::string string;
NpcBehaviourNode* left;
NpcBehaviourNode* right;
NpcBehaviourNode() : type(), number(0), left(nullptr), right(nullptr) { }
~NpcBehaviourNode() {
delete left;
delete right;
}
NpcBehaviourNode* clone() {
NpcBehaviourNode* copy = new NpcBehaviourNode();
copy->type = type;
copy->number = number;
copy->string = string;
if (left) {
copy->left = left->clone();
}
if (right) {
copy->right = right->clone();
}
return copy;
}
};
struct NpcBehaviourCondition
{
NpcBehaviourType_t type;
BehaviourSituation_t situation;
std::string string;
int32_t number;
NpcBehaviourNode* expression;
NpcBehaviourCondition() : type(), situation(SITUATION_NONE), string(), number(0), expression(nullptr) {}
~NpcBehaviourCondition() {
delete expression;
}
//non-copyable
NpcBehaviourCondition(const NpcBehaviourCondition&) = delete;
NpcBehaviourCondition& operator=(const NpcBehaviourCondition&) = delete;
bool setCondition(NpcBehaviourType_t _type, int32_t _number, const std::string& _string);
};
struct NpcBehaviourAction
{
NpcBehaviourType_t type;
std::string string;
int32_t number;
NpcBehaviourNode* expression;
NpcBehaviourNode* expression2;
NpcBehaviourNode* expression3;
NpcBehaviourAction() :
type(),
string(),
number(0),
expression(nullptr),
expression2(nullptr),
expression3(nullptr) {}
~NpcBehaviourAction() {
delete expression;
delete expression2;
delete expression3;
}
NpcBehaviourAction* clone() {
NpcBehaviourAction* copy = new NpcBehaviourAction();
copy->type = type;
copy->string = string;
copy->number = number;
if (expression) {
copy->expression = expression->clone();
}
if (expression2) {
copy->expression2 = expression2->clone();
}
if (expression3) {
copy->expression3 = expression3->clone();
}
return copy;
}
};
struct NpcBehaviour
{
BehaviourSituation_t situation = SITUATION_NONE;
uint32_t priority = 0;
std::vector<NpcBehaviourCondition*> conditions;
std::vector<NpcBehaviourAction*> actions;
NpcBehaviour() = default;
~NpcBehaviour() {
for (auto condition : conditions) {
delete condition;
}
for (auto action : actions) {
delete action;
}
}
//non-copyable
NpcBehaviour(const NpcBehaviour&) = delete;
NpcBehaviour& operator=(const NpcBehaviour&) = delete;
};
struct NpcQueueEntry
{
uint32_t playerId;
std::string text;
};
class BehaviourDatabase
{
public:
BehaviourDatabase(Npc* _npc);
~BehaviourDatabase();
// non-copyable
BehaviourDatabase(const BehaviourDatabase&) = delete;
BehaviourDatabase& operator=(const BehaviourDatabase&) = delete;
bool loadDatabase(ScriptReader& script);
bool loadBehaviour(ScriptReader& script);
bool loadConditions(ScriptReader& script, NpcBehaviour* behaviour);
bool loadActions(ScriptReader& script, NpcBehaviour* behaviour);
NpcBehaviourNode* readValue(ScriptReader& script);
NpcBehaviourNode* readFactor(ScriptReader& script, NpcBehaviourNode* nextNode);
void react(BehaviourSituation_t situation, Player* player, const std::string& message);
static bool compareBehaviour(const NpcBehaviour* left, const NpcBehaviour* right) {
return left->priority >= right->priority;
}
private:
bool checkCondition(const NpcBehaviourCondition* condition, Player* player, const std::string& message);
void checkAction(const NpcBehaviourAction* action, Player* player, const std::string& message);
int32_t evaluate(NpcBehaviourNode* node, Player* player, const std::string& message = "");
int32_t checkOperation(Player* player, NpcBehaviourNode* node, const std::string& message);
int32_t searchDigit(const std::string& message);
bool searchWord(const std::string& pattern, const std::string& message);
std::string parseResponse(Player* player, const std::string& message);
void attendCustomer(uint32_t playerId);
void queueCustomer(uint32_t playerId, const std::string& message);
void idle();
void reset();
int32_t topic;
int32_t data;
int32_t type;
int32_t price;
int32_t amount;
int32_t delay;
std::string string;
Npc* npc = nullptr;
NpcBehaviour* previousBehaviour = nullptr;
NpcBehaviour* priorityBehaviour = nullptr;
std::list<NpcQueueEntry> queueList;
std::vector<uint32_t> delayedEvents;
std::list<NpcBehaviour*> behaviourEntries;
std::recursive_mutex mutex;
};
#endif

591
src/chat.cpp Normal file
View File

@@ -0,0 +1,591 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "chat.h"
#include "game.h"
#include "pugicast.h"
#include "scheduler.h"
extern Chat* g_chat;
extern Game g_game;
bool PrivateChatChannel::isInvited(uint32_t guid) const
{
if (guid == getOwner()) {
return true;
}
return invites.find(guid) != invites.end();
}
bool PrivateChatChannel::removeInvite(uint32_t guid)
{
return invites.erase(guid) != 0;
}
void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer)
{
auto result = invites.emplace(invitePlayer.getGUID(), &invitePlayer);
if (!result.second) {
return;
}
std::ostringstream ss;
ss << player.getName() << " invites you to " << (player.getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " private chat channel.";
invitePlayer.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
ss.str(std::string());
ss << invitePlayer.getName() << " has been invited.";
player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
}
void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlayer)
{
if (!removeInvite(excludePlayer.getGUID())) {
return;
}
removeUser(excludePlayer);
std::ostringstream ss;
ss << excludePlayer.getName() << " has been excluded.";
player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
excludePlayer.sendClosePrivate(id);
}
void PrivateChatChannel::closeChannel() const
{
for (const auto& it : users) {
it.second->sendClosePrivate(id);
}
}
bool ChatChannel::addUser(Player& player)
{
if (users.find(player.getID()) != users.end()) {
return false;
}
if (!executeOnJoinEvent(player)) {
return false;
}
users[player.getID()] = &player;
return true;
}
bool ChatChannel::removeUser(const Player& player)
{
auto iter = users.find(player.getID());
if (iter == users.end()) {
return false;
}
users.erase(iter);
executeOnLeaveEvent(player);
return true;
}
bool ChatChannel::talk(const Player& fromPlayer, SpeakClasses type, const std::string& text)
{
if (users.find(fromPlayer.getID()) == users.end()) {
return false;
}
for (const auto& it : users) {
it.second->sendToChannel(&fromPlayer, type, text, id);
}
return true;
}
bool ChatChannel::executeCanJoinEvent(const Player& player)
{
if (canJoinEvent == -1) {
return true;
}
//canJoin(player)
LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - CanJoinChannelEvent::execute] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(canJoinEvent, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(canJoinEvent);
LuaScriptInterface::pushUserdata(L, &player);
LuaScriptInterface::setMetatable(L, -1, "Player");
return scriptInterface->callFunction(1);
}
bool ChatChannel::executeOnJoinEvent(const Player& player)
{
if (onJoinEvent == -1) {
return true;
}
//onJoin(player)
LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - OnJoinChannelEvent::execute] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(onJoinEvent, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(onJoinEvent);
LuaScriptInterface::pushUserdata(L, &player);
LuaScriptInterface::setMetatable(L, -1, "Player");
return scriptInterface->callFunction(1);
}
bool ChatChannel::executeOnLeaveEvent(const Player& player)
{
if (onLeaveEvent == -1) {
return true;
}
//onLeave(player)
LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - OnLeaveChannelEvent::execute] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(onLeaveEvent, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(onLeaveEvent);
LuaScriptInterface::pushUserdata(L, &player);
LuaScriptInterface::setMetatable(L, -1, "Player");
return scriptInterface->callFunction(1);
}
bool ChatChannel::executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message)
{
if (onSpeakEvent == -1) {
return true;
}
//onSpeak(player, type, message)
LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - OnSpeakChannelEvent::execute] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(onSpeakEvent, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(onSpeakEvent);
LuaScriptInterface::pushUserdata(L, &player);
LuaScriptInterface::setMetatable(L, -1, "Player");
lua_pushnumber(L, type);
LuaScriptInterface::pushString(L, message);
bool result = false;
int size0 = lua_gettop(L);
int ret = scriptInterface->protectedCall(L, 3, 1);
if (ret != 0) {
LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L));
} else if (lua_gettop(L) > 0) {
if (lua_isboolean(L, -1)) {
result = LuaScriptInterface::getBoolean(L, -1);
} else if (lua_isnumber(L, -1)) {
result = true;
type = LuaScriptInterface::getNumber<SpeakClasses>(L, -1);
}
lua_pop(L, 1);
}
if ((lua_gettop(L) + 4) != size0) {
LuaScriptInterface::reportError(nullptr, "Stack size changed!");
}
scriptInterface->resetScriptEnv();
return result;
}
Chat::Chat():
scriptInterface("Chat Interface"),
dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel")
{
scriptInterface.initState();
}
bool Chat::load()
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file("data/chatchannels/chatchannels.xml");
if (!result) {
printXMLError("Error - Chat::load", "data/chatchannels/chatchannels.xml", result);
return false;
}
std::forward_list<uint16_t> removedChannels;
for (auto& channelEntry : normalChannels) {
ChatChannel& channel = channelEntry.second;
channel.onSpeakEvent = -1;
channel.canJoinEvent = -1;
channel.onJoinEvent = -1;
channel.onLeaveEvent = -1;
removedChannels.push_front(channelEntry.first);
}
for (auto channelNode : doc.child("channels").children()) {
ChatChannel channel(pugi::cast<uint16_t>(channelNode.attribute("id").value()), channelNode.attribute("name").as_string());
channel.publicChannel = channelNode.attribute("public").as_bool();
pugi::xml_attribute scriptAttribute = channelNode.attribute("script");
if (scriptAttribute) {
if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) {
channel.onSpeakEvent = scriptInterface.getEvent("onSpeak");
channel.canJoinEvent = scriptInterface.getEvent("canJoin");
channel.onJoinEvent = scriptInterface.getEvent("onJoin");
channel.onLeaveEvent = scriptInterface.getEvent("onLeave");
} else {
std::cout << "[Warning - Chat::load] Can not load script: " << scriptAttribute.as_string() << std::endl;
}
}
removedChannels.remove(channel.id);
normalChannels[channel.id] = channel;
}
for (uint16_t channelId : removedChannels) {
normalChannels.erase(channelId);
}
return true;
}
ChatChannel* Chat::createChannel(const Player& player, uint16_t channelId)
{
if (getChannel(player, channelId)) {
return nullptr;
}
switch (channelId) {
case CHANNEL_GUILD: {
Guild* guild = player.getGuild();
if (guild) {
auto ret = guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName())));
return &ret.first->second;
}
break;
}
case CHANNEL_PARTY: {
Party* party = player.getParty();
if (party) {
auto ret = partyChannels.emplace(std::make_pair(party, ChatChannel(channelId, "Party")));
return &ret.first->second;
}
break;
}
case CHANNEL_PRIVATE: {
//only 1 private channel for each premium player
if (!player.isPremium() || getPrivateChannel(player)) {
return nullptr;
}
//find a free private channel slot
for (uint16_t i = 100; i < 10000; ++i) {
auto ret = privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player.getName() + "'s Channel")));
if (ret.second) { //second is a bool that indicates that a new channel has been placed in the map
auto& newChannel = (*ret.first).second;
newChannel.setOwner(player.getGUID());
return &newChannel;
}
}
break;
}
default:
break;
}
return nullptr;
}
bool Chat::deleteChannel(const Player& player, uint16_t channelId)
{
switch (channelId) {
case CHANNEL_GUILD: {
Guild* guild = player.getGuild();
if (!guild) {
return false;
}
auto it = guildChannels.find(guild->getId());
if (it == guildChannels.end()) {
return false;
}
guildChannels.erase(it);
break;
}
case CHANNEL_PARTY: {
Party* party = player.getParty();
if (!party) {
return false;
}
auto it = partyChannels.find(party);
if (it == partyChannels.end()) {
return false;
}
partyChannels.erase(it);
break;
}
default: {
auto it = privateChannels.find(channelId);
if (it == privateChannels.end()) {
return false;
}
it->second.closeChannel();
privateChannels.erase(it);
break;
}
}
return true;
}
ChatChannel* Chat::addUserToChannel(Player& player, uint16_t channelId)
{
ChatChannel* channel = getChannel(player, channelId);
if (channel && channel->addUser(player)) {
return channel;
}
return nullptr;
}
bool Chat::removeUserFromChannel(const Player& player, uint16_t channelId)
{
ChatChannel* channel = getChannel(player, channelId);
if (!channel || !channel->removeUser(player)) {
return false;
}
if (channel->getOwner() == player.getGUID()) {
deleteChannel(player, channelId);
}
return true;
}
void Chat::removeUserFromAllChannels(const Player& player)
{
for (auto& it : normalChannels) {
it.second.removeUser(player);
}
for (auto& it : partyChannels) {
it.second.removeUser(player);
}
for (auto& it : guildChannels) {
it.second.removeUser(player);
}
auto it = privateChannels.begin();
while (it != privateChannels.end()) {
PrivateChatChannel* channel = &it->second;
channel->removeInvite(player.getGUID());
channel->removeUser(player);
if (channel->getOwner() == player.getGUID()) {
channel->closeChannel();
it = privateChannels.erase(it);
} else {
++it;
}
}
}
bool Chat::talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId)
{
ChatChannel* channel = getChannel(player, channelId);
if (!channel) {
return false;
}
if (channelId == CHANNEL_GUILD) {
const GuildRank* rank = player.getGuildRank();
if (rank && rank->level > 1) {
type = TALKTYPE_CHANNEL_O;
} else if (type != TALKTYPE_CHANNEL_Y) {
type = TALKTYPE_CHANNEL_Y;
}
} else if (type != TALKTYPE_CHANNEL_Y && (channelId == CHANNEL_PRIVATE || channelId == CHANNEL_PARTY)) {
type = TALKTYPE_CHANNEL_Y;
}
if (!channel->executeOnSpeakEvent(player, type, text)) {
return false;
}
return channel->talk(player, type, text);
}
ChannelList Chat::getChannelList(const Player& player)
{
ChannelList list;
if (player.getGuild()) {
ChatChannel* channel = getChannel(player, CHANNEL_GUILD);
if (channel) {
list.push_back(channel);
} else {
channel = createChannel(player, CHANNEL_GUILD);
if (channel) {
list.push_back(channel);
}
}
}
if (player.getParty()) {
ChatChannel* channel = getChannel(player, CHANNEL_PARTY);
if (channel) {
list.push_back(channel);
} else {
channel = createChannel(player, CHANNEL_PARTY);
if (channel) {
list.push_back(channel);
}
}
}
for (const auto& it : normalChannels) {
ChatChannel* channel = getChannel(player, it.first);
if (channel) {
list.push_back(channel);
}
}
bool hasPrivate = false;
for (auto& it : privateChannels) {
if (PrivateChatChannel* channel = &it.second) {
uint32_t guid = player.getGUID();
if (channel->isInvited(guid)) {
list.push_back(channel);
}
if (channel->getOwner() == guid) {
hasPrivate = true;
}
}
}
if (!hasPrivate && player.isPremium()) {
list.push_front(&dummyPrivate);
}
return list;
}
ChatChannel* Chat::getChannel(const Player& player, uint16_t channelId)
{
switch (channelId) {
case CHANNEL_GUILD: {
Guild* guild = player.getGuild();
if (guild) {
auto it = guildChannels.find(guild->getId());
if (it != guildChannels.end()) {
return &it->second;
}
}
break;
}
case CHANNEL_PARTY: {
Party* party = player.getParty();
if (party) {
auto it = partyChannels.find(party);
if (it != partyChannels.end()) {
return &it->second;
}
}
break;
}
default: {
auto it = normalChannels.find(channelId);
if (it != normalChannels.end()) {
ChatChannel& channel = it->second;
if (!channel.executeCanJoinEvent(player)) {
return nullptr;
}
return &channel;
} else {
auto it2 = privateChannels.find(channelId);
if (it2 != privateChannels.end() && it2->second.isInvited(player.getGUID())) {
return &it2->second;
}
}
break;
}
}
return nullptr;
}
ChatChannel* Chat::getGuildChannelById(uint32_t guildId)
{
auto it = guildChannels.find(guildId);
if (it == guildChannels.end()) {
return nullptr;
}
return &it->second;
}
ChatChannel* Chat::getChannelById(uint16_t channelId)
{
auto it = normalChannels.find(channelId);
if (it == normalChannels.end()) {
return nullptr;
}
return &it->second;
}
PrivateChatChannel* Chat::getPrivateChannel(const Player& player)
{
for (auto& it : privateChannels) {
if (it.second.getOwner() == player.getGUID()) {
return &it.second;
}
}
return nullptr;
}

161
src/chat.h Normal file
View File

@@ -0,0 +1,161 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628
#define FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628
#include "const.h"
#include "luascript.h"
class Party;
class Player;
typedef std::map<uint32_t, Player*> UsersMap;
typedef std::map<uint32_t, const Player*> InvitedMap;
class ChatChannel
{
public:
ChatChannel() = default;
ChatChannel(uint16_t channelId, std::string channelName):
name(std::move(channelName)),
id(channelId) {}
virtual ~ChatChannel() = default;
bool addUser(Player& player);
bool removeUser(const Player& player);
bool talk(const Player& fromPlayer, SpeakClasses type, const std::string& text);
const std::string& getName() const {
return name;
}
uint16_t getId() const {
return id;
}
const UsersMap& getUsers() const {
return users;
}
virtual const InvitedMap* getInvitedUsers() const {
return nullptr;
}
virtual uint32_t getOwner() const {
return 0;
}
bool isPublicChannel() const { return publicChannel; }
bool executeOnJoinEvent(const Player& player);
bool executeCanJoinEvent(const Player& player);
bool executeOnLeaveEvent(const Player& player);
bool executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message);
protected:
UsersMap users;
std::string name;
int32_t canJoinEvent = -1;
int32_t onJoinEvent = -1;
int32_t onLeaveEvent = -1;
int32_t onSpeakEvent = -1;
uint16_t id;
bool publicChannel = false;
friend class Chat;
};
class PrivateChatChannel final : public ChatChannel
{
public:
PrivateChatChannel(uint16_t channelId, std::string channelName) : ChatChannel(channelId, channelName) {}
uint32_t getOwner() const final {
return owner;
}
void setOwner(uint32_t owner) {
this->owner = owner;
}
bool isInvited(uint32_t guid) const;
void invitePlayer(const Player& player, Player& invitePlayer);
void excludePlayer(const Player& player, Player& excludePlayer);
bool removeInvite(uint32_t guid);
void closeChannel() const;
const InvitedMap* getInvitedUsers() const final {
return &invites;
}
protected:
InvitedMap invites;
uint32_t owner = 0;
};
typedef std::list<ChatChannel*> ChannelList;
class Chat
{
public:
Chat();
// non-copyable
Chat(const Chat&) = delete;
Chat& operator=(const Chat&) = delete;
bool load();
ChatChannel* createChannel(const Player& player, uint16_t channelId);
bool deleteChannel(const Player& player, uint16_t channelId);
ChatChannel* addUserToChannel(Player& player, uint16_t channelId);
bool removeUserFromChannel(const Player& player, uint16_t channelId);
void removeUserFromAllChannels(const Player& player);
bool talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId);
ChannelList getChannelList(const Player& player);
ChatChannel* getChannel(const Player& player, uint16_t channelId);
ChatChannel* getChannelById(uint16_t channelId);
ChatChannel* getGuildChannelById(uint32_t guildId);
PrivateChatChannel* getPrivateChannel(const Player& player);
LuaScriptInterface* getScriptInterface() {
return &scriptInterface;
}
private:
std::map<uint16_t, ChatChannel> normalChannels;
std::map<uint16_t, PrivateChatChannel> privateChannels;
std::map<Party*, ChatChannel> partyChannels;
std::map<uint32_t, ChatChannel> guildChannels;
LuaScriptInterface scriptInterface;
PrivateChatChannel dummyPrivate;
};
#endif

1911
src/combat.cpp Normal file

File diff suppressed because it is too large Load Diff

397
src/combat.h Normal file
View File

@@ -0,0 +1,397 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_COMBAT_H_B02CE79230FC43708699EE91FCC8F7CC
#define FS_COMBAT_H_B02CE79230FC43708699EE91FCC8F7CC
#include "thing.h"
#include "condition.h"
#include "map.h"
#include "baseevents.h"
class Condition;
class Creature;
class Item;
struct Position;
//for luascript callback
class ValueCallback final : public CallBack
{
public:
explicit ValueCallback(formulaType_t type): type(type) {}
void getMinMaxValues(Player* player, CombatDamage& damage, bool useCharges) const;
protected:
formulaType_t type;
};
class TileCallback final : public CallBack
{
public:
void onTileCombat(Creature* creature, Tile* tile) const;
protected:
formulaType_t type;
};
class TargetCallback final : public CallBack
{
public:
void onTargetCombat(Creature* creature, Creature* target) const;
protected:
formulaType_t type;
};
struct CombatParams {
std::forward_list<std::unique_ptr<const Condition>> conditionList;
std::unique_ptr<ValueCallback> valueCallback;
std::unique_ptr<TileCallback> tileCallback;
std::unique_ptr<TargetCallback> targetCallback;
uint16_t itemId = 0;
uint16_t decreaseDamage = 0;
uint16_t maximumDecreasedDamage = 0;
ConditionType_t dispelType = CONDITION_NONE;
CombatType_t combatType = COMBAT_NONE;
uint8_t impactEffect = CONST_ME_NONE;
uint8_t distanceEffect = CONST_ANI_NONE;
bool blockedByArmor = false;
bool blockedByShield = false;
bool targetCasterOrTopMost = false;
bool aggressive = true;
bool useCharges = false;
};
struct Impact
{
Creature* actor = nullptr;
virtual void handleCreature(Creature*) {
//
}
};
struct DamageImpact : Impact
{
CombatParams params;
CombatDamage damage;
void handleCreature(Creature* target) final;
};
struct SpeedImpact : Impact
{
int32_t percent = 0;
int32_t duration = 0;
void handleCreature(Creature* target) final;
};
struct DunkenImpact : Impact
{
int32_t duration = 0;
void handleCreature(Creature* target) final;
};
typedef bool (*COMBATFUNC)(Creature*, Creature*, const CombatParams&, CombatDamage*);
class MatrixArea
{
public:
MatrixArea(uint32_t rows, uint32_t cols): centerX(0), centerY(0), rows(rows), cols(cols) {
data_ = new bool*[rows];
for (uint32_t row = 0; row < rows; ++row) {
data_[row] = new bool[cols];
for (uint32_t col = 0; col < cols; ++col) {
data_[row][col] = 0;
}
}
}
MatrixArea(const MatrixArea& rhs) {
centerX = rhs.centerX;
centerY = rhs.centerY;
rows = rhs.rows;
cols = rhs.cols;
data_ = new bool*[rows];
for (uint32_t row = 0; row < rows; ++row) {
data_[row] = new bool[cols];
for (uint32_t col = 0; col < cols; ++col) {
data_[row][col] = rhs.data_[row][col];
}
}
}
~MatrixArea() {
for (uint32_t row = 0; row < rows; ++row) {
delete[] data_[row];
}
delete[] data_;
}
// non-assignable
MatrixArea& operator=(const MatrixArea&) = delete;
void setValue(uint32_t row, uint32_t col, bool value) const {
data_[row][col] = value;
}
bool getValue(uint32_t row, uint32_t col) const {
return data_[row][col];
}
void setCenter(uint32_t y, uint32_t x) {
centerX = x;
centerY = y;
}
void getCenter(uint32_t& y, uint32_t& x) const {
x = centerX;
y = centerY;
}
uint32_t getRows() const {
return rows;
}
uint32_t getCols() const {
return cols;
}
inline const bool* operator[](uint32_t i) const {
return data_[i];
}
inline bool* operator[](uint32_t i) {
return data_[i];
}
protected:
uint32_t centerX;
uint32_t centerY;
uint32_t rows;
uint32_t cols;
bool** data_;
};
class AreaCombat
{
public:
AreaCombat() = default;
AreaCombat(const AreaCombat& rhs);
~AreaCombat() {
clear();
}
// non-assignable
AreaCombat& operator=(const AreaCombat&) = delete;
void getList(const Position& centerPos, const Position& targetPos, std::forward_list<Tile*>& list) const;
void setupArea(const std::list<uint32_t>& list, uint32_t rows);
void setupArea(int32_t length, int32_t spread);
void setupArea(int32_t radius);
void setupExtArea(const std::list<uint32_t>& list, uint32_t rows);
void clear();
protected:
enum MatrixOperation_t {
MATRIXOPERATION_COPY,
MATRIXOPERATION_MIRROR,
MATRIXOPERATION_FLIP,
MATRIXOPERATION_ROTATE90,
MATRIXOPERATION_ROTATE180,
MATRIXOPERATION_ROTATE270,
};
MatrixArea* createArea(const std::list<uint32_t>& list, uint32_t rows);
void copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const;
MatrixArea* getArea(const Position& centerPos, const Position& targetPos) const {
int32_t dx = Position::getOffsetX(targetPos, centerPos);
int32_t dy = Position::getOffsetY(targetPos, centerPos);
Direction dir;
if (dx < 0) {
dir = DIRECTION_WEST;
} else if (dx > 0) {
dir = DIRECTION_EAST;
} else if (dy < 0) {
dir = DIRECTION_NORTH;
} else {
dir = DIRECTION_SOUTH;
}
if (hasExtArea) {
if (dx < 0 && dy < 0) {
dir = DIRECTION_NORTHWEST;
} else if (dx > 0 && dy < 0) {
dir = DIRECTION_NORTHEAST;
} else if (dx < 0 && dy > 0) {
dir = DIRECTION_SOUTHWEST;
} else if (dx > 0 && dy > 0) {
dir = DIRECTION_SOUTHEAST;
}
}
auto it = areas.find(dir);
if (it == areas.end()) {
return nullptr;
}
return it->second;
}
std::map<Direction, MatrixArea*> areas;
bool hasExtArea = false;
};
class Combat
{
public:
Combat() = default;
// non-copyable
Combat(const Combat&) = delete;
Combat& operator=(const Combat&) = delete;
static int32_t computeDamage(Creature* creature, int32_t strength, int32_t variation);
static int32_t getTotalDamage(int32_t attackSkill, int32_t attackValue, fightMode_t fightMode);
static bool attack(Creature* attacker, Creature* target);
static bool closeAttack(Creature* attacker, Creature* target, fightMode_t fightMode);
static bool rangeAttack(Creature* attacker, Creature* target, fightMode_t fightMode);
static void circleShapeSpell(Creature* attacker, const Position& toPos, int32_t range, int32_t animation, int32_t radius, DamageImpact* impact, int32_t effect);
static void getAttackValue(Creature* creature, uint32_t& attackValue, uint32_t& skillValue, uint8_t& skill);
static bool doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params);
static void doCombatHealth(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params);
static void doCombatMana(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params);
static void doCombatMana(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params);
static void doCombatCondition(Creature* caster, Creature* target, const CombatParams& params);
static void doCombatCondition(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params);
static void doCombatDispel(Creature* caster, Creature* target, const CombatParams& params);
static void doCombatDispel(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params);
static void getCombatArea(const Position& centerPos, const Position& targetPos, const AreaCombat* area, std::forward_list<Tile*>& list);
static bool isInPvpZone(const Creature* attacker, const Creature* target);
static bool isProtected(const Player* attacker, const Player* target);
static bool isPlayerCombat(const Creature* target);
static CombatType_t ConditionToDamageType(ConditionType_t type);
static ConditionType_t DamageToConditionType(CombatType_t type);
static ReturnValue canTargetCreature(Player* attacker, Creature* target);
static ReturnValue canDoCombat(Creature* caster, Tile* tile, bool aggressive);
static ReturnValue canDoCombat(Creature* attacker, Creature* target);
static void postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params);
static void addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect);
void doCombat(Creature* caster, Creature* target) const;
void doCombat(Creature* caster, const Position& pos) const;
bool setCallback(CallBackParam_t key);
CallBack* getCallback(CallBackParam_t key);
bool setParam(CombatParam_t param, uint32_t value);
void setArea(AreaCombat* area) {
this->area.reset(area);
}
bool hasArea() const {
return area != nullptr;
}
void setCondition(const Condition* condition) {
params.conditionList.emplace_front(condition);
}
void setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb);
void postCombatEffects(Creature* caster, const Position& pos) const {
postCombatEffects(caster, pos, params);
}
protected:
static bool canUseWeapon(Player* player, Item* weapon);
static void postWeaponEffects(Player* player, Item* weapon);
static void doCombatDefault(Creature* caster, Creature* target, const CombatParams& params);
static void CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, COMBATFUNC func, CombatDamage* data);
static bool CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data);
static bool CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* damage);
static bool CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data);
static bool CombatDispelFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data);
static bool CombatNullFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data);
static void combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params);
CombatDamage getCombatDamage(Creature* creature) const;
//configureable
CombatParams params;
//formula variables
formulaType_t formulaType = COMBAT_FORMULA_UNDEFINED;
double mina = 0.0;
double minb = 0.0;
double maxa = 0.0;
double maxb = 0.0;
std::unique_ptr<AreaCombat> area;
};
class MagicField final : public Item
{
public:
explicit MagicField(uint16_t type) : Item(type), createTime(OTSYS_TIME()) {}
MagicField* getMagicField() final {
return this;
}
const MagicField* getMagicField() const final {
return this;
}
bool isReplaceable() const {
return Item::items[getID()].replaceable;
}
CombatType_t getCombatType() const {
const ItemType& it = items[getID()];
return it.combatType;
}
void onStepInField(Creature* creature);
private:
int64_t createTime;
};
#endif

328
src/commands.cpp Normal file
View File

@@ -0,0 +1,328 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 <fstream>
#include "commands.h"
#include "player.h"
#include "npc.h"
#include "game.h"
#include "actions.h"
#include "iologindata.h"
#include "configmanager.h"
#include "spells.h"
#include "movement.h"
#include "globalevent.h"
#include "monster.h"
#include "scheduler.h"
#include "pugicast.h"
extern ConfigManager g_config;
extern Actions* g_actions;
extern Monsters g_monsters;
extern TalkActions* g_talkActions;
extern MoveEvents* g_moveEvents;
extern Spells* g_spells;
extern Game g_game;
extern CreatureEvents* g_creatureEvents;
extern GlobalEvents* g_globalEvents;
extern Chat* g_chat;
extern LuaEnvironment g_luaEnvironment;
s_defcommands Commands::defined_commands[] = {
// TODO: move all commands to talkactions
//admin commands
{"/reload", &Commands::reloadInfo},
{"/raid", &Commands::forceRaid},
// player commands
{"!sellhouse", &Commands::sellHouse}
};
Commands::Commands()
{
// set up command map
for (auto& command : defined_commands) {
commandMap[command.name] = new Command(command.f, 1, ACCOUNT_TYPE_GOD, true);
}
}
Commands::~Commands()
{
for (const auto& it : commandMap) {
delete it.second;
}
}
bool Commands::loadFromXml()
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file("data/XML/commands.xml");
if (!result) {
printXMLError("Error - Commands::loadFromXml", "data/XML/commands.xml", result);
return false;
}
for (auto commandNode : doc.child("commands").children()) {
pugi::xml_attribute cmdAttribute = commandNode.attribute("cmd");
if (!cmdAttribute) {
std::cout << "[Warning - Commands::loadFromXml] Missing cmd" << std::endl;
continue;
}
auto it = commandMap.find(cmdAttribute.as_string());
if (it == commandMap.end()) {
std::cout << "[Warning - Commands::loadFromXml] Unknown command " << cmdAttribute.as_string() << std::endl;
continue;
}
Command* command = it->second;
pugi::xml_attribute groupAttribute = commandNode.attribute("group");
if (groupAttribute) {
command->groupId = pugi::cast<uint32_t>(groupAttribute.value());
} else {
std::cout << "[Warning - Commands::loadFromXml] Missing group for command " << it->first << std::endl;
}
pugi::xml_attribute acctypeAttribute = commandNode.attribute("acctype");
if (acctypeAttribute) {
command->accountType = static_cast<AccountType_t>(pugi::cast<uint32_t>(acctypeAttribute.value()));
} else {
std::cout << "[Warning - Commands::loadFromXml] Missing acctype for command " << it->first << std::endl;
}
pugi::xml_attribute logAttribute = commandNode.attribute("log");
if (logAttribute) {
command->log = booleanString(logAttribute.as_string());
} else {
std::cout << "[Warning - Commands::loadFromXml] Missing log for command " << it->first << std::endl;
}
g_game.addCommandTag(it->first.front());
}
return true;
}
bool Commands::reload()
{
for (const auto& it : commandMap) {
Command* command = it.second;
command->groupId = 1;
command->accountType = ACCOUNT_TYPE_GOD;
command->log = true;
}
g_game.resetCommandTag();
return loadFromXml();
}
bool Commands::exeCommand(Player& player, const std::string& cmd)
{
std::string str_command;
std::string str_param;
std::string::size_type loc = cmd.find(' ', 0);
if (loc != std::string::npos) {
str_command = std::string(cmd, 0, loc);
str_param = std::string(cmd, (loc + 1), cmd.size() - loc - 1);
} else {
str_command = cmd;
}
//find command
auto it = commandMap.find(str_command);
if (it == commandMap.end()) {
return false;
}
Command* command = it->second;
if (command->groupId > player.getGroup()->id || command->accountType > player.getAccountType()) {
if (player.getGroup()->access) {
player.sendTextMessage(MESSAGE_STATUS_SMALL, "You can not execute this command.");
}
return false;
}
//execute command
CommandFunc cfunc = command->f;
(this->*cfunc)(player, str_param);
if (command->log) {
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, cmd);
std::ostringstream logFile;
logFile << "data/logs/" << player.getName() << " commands.log";
std::ofstream out(logFile.str(), std::ios::app);
if (out.is_open()) {
time_t ticks = time(nullptr);
const tm* now = localtime(&ticks);
char buf[32];
strftime(buf, sizeof(buf), "%d/%m/%Y %H:%M", now);
out << '[' << buf << "] " << cmd << std::endl;
out.close();
}
}
return true;
}
void Commands::reloadInfo(Player& player, const std::string& param)
{
std::string tmpParam = asLowerCaseString(param);
if (tmpParam == "action" || tmpParam == "actions") {
g_actions->reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded actions.");
} else if (tmpParam == "config" || tmpParam == "configuration") {
g_config.reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded config.");
} else if (tmpParam == "command" || tmpParam == "commands") {
reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded commands.");
} else if (tmpParam == "creaturescript" || tmpParam == "creaturescripts") {
g_creatureEvents->reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded creature scripts.");
} else if (tmpParam == "monster" || tmpParam == "monsters") {
g_monsters.reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded monsters.");
} else if (tmpParam == "move" || tmpParam == "movement" || tmpParam == "movements"
|| tmpParam == "moveevents" || tmpParam == "moveevent") {
g_moveEvents->reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded movements.");
} else if (tmpParam == "npc" || tmpParam == "npcs") {
Npcs::reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded npcs.");
} else if (tmpParam == "raid" || tmpParam == "raids") {
g_game.raids.reload();
g_game.raids.startup();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded raids.");
} else if (tmpParam == "spell" || tmpParam == "spells") {
g_spells->reload();
g_monsters.reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded spells.");
} else if (tmpParam == "talk" || tmpParam == "talkaction" || tmpParam == "talkactions") {
g_talkActions->reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded talk actions.");
} else if (tmpParam == "items") {
Item::items.reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded items.");
} else if (tmpParam == "globalevents" || tmpParam == "globalevent") {
g_globalEvents->reload();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded globalevents.");
} else if (tmpParam == "chat" || tmpParam == "channel" || tmpParam == "chatchannels") {
g_chat->load();
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded chatchannels.");
} else if (tmpParam == "global") {
g_luaEnvironment.loadFile("data/global.lua");
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded global.lua.");
} else {
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reload type not found.");
}
lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0);
}
void Commands::sellHouse(Player& player, const std::string& param)
{
Player* tradePartner = g_game.getPlayerByName(param);
if (!tradePartner || tradePartner == &player) {
player.sendCancelMessage("Trade player not found.");
return;
}
if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player.getPosition())) {
player.sendCancelMessage("Trade player is too far away.");
return;
}
if (!tradePartner->isPremium()) {
player.sendCancelMessage("Trade player does not have a premium account.");
return;
}
HouseTile* houseTile = dynamic_cast<HouseTile*>(player.getTile());
if (!houseTile) {
player.sendCancelMessage("You must stand in your house to initiate the trade.");
return;
}
House* house = houseTile->getHouse();
if (!house || house->getOwner() != player.getGUID()) {
player.sendCancelMessage("You don't own this house.");
return;
}
if (g_game.map.houses.getHouseByPlayerId(tradePartner->getGUID())) {
player.sendCancelMessage("Trade player already owns a house.");
return;
}
if (IOLoginData::hasBiddedOnHouse(tradePartner->getGUID())) {
player.sendCancelMessage("Trade player is currently the highest bidder of an auctioned house.");
return;
}
Item* transferItem = house->getTransferItem();
if (!transferItem) {
player.sendCancelMessage("You can not trade this house.");
return;
}
transferItem->getParent()->setParent(&player);
if (!g_game.internalStartTrade(&player, tradePartner, transferItem)) {
house->resetTransferItem();
}
}
void Commands::forceRaid(Player& player, const std::string& param)
{
Raid* raid = g_game.raids.getRaidByName(param);
if (!raid || !raid->isLoaded()) {
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "No such raid exists.");
return;
}
if (g_game.raids.getRunning()) {
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Another raid is already being executed.");
return;
}
g_game.raids.setRunning(raid);
RaidEvent* event = raid->getNextRaidEvent();
if (!event) {
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "The raid does not contain any data.");
return;
}
raid->setState(RAIDSTATE_EXECUTING);
uint32_t ticks = event->getDelay();
if (ticks > 0) {
g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Raid::executeRaidEvent, raid, event)));
} else {
g_dispatcher.addTask(createTask(std::bind(&Raid::executeRaidEvent, raid, event)));
}
player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Raid started.");
}

74
src/commands.h Normal file
View File

@@ -0,0 +1,74 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_COMMANDS_H_C95A575CCADF434699D26CD042690970
#define FS_COMMANDS_H_C95A575CCADF434699D26CD042690970
#include "enums.h"
class Player;
struct Command;
struct s_defcommands;
class Commands
{
public:
Commands();
~Commands();
// non-copyable
Commands(const Commands&) = delete;
Commands& operator=(const Commands&) = delete;
bool loadFromXml();
bool reload();
bool exeCommand(Player& player, const std::string& cmd);
protected:
//commands
void reloadInfo(Player& player, const std::string& param);
void sellHouse(Player& player, const std::string& param);
void forceRaid(Player& player, const std::string& param);
//table of commands
static s_defcommands defined_commands[];
std::map<std::string, Command*> commandMap;
};
typedef void (Commands::*CommandFunc)(Player&, const std::string&);
struct Command {
Command(CommandFunc f, uint32_t groupId, AccountType_t accountType, bool log)
: f(f), groupId(groupId), accountType(accountType), log(log) {}
CommandFunc f;
uint32_t groupId;
AccountType_t accountType;
bool log;
};
struct s_defcommands {
const char* name;
CommandFunc f;
};
#endif

1297
src/condition.cpp Normal file

File diff suppressed because it is too large Load Diff

377
src/condition.h Normal file
View File

@@ -0,0 +1,377 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_CONDITION_H_F92FF8BDDD5B4EA59E2B1BB5C9C0A086
#define FS_CONDITION_H_F92FF8BDDD5B4EA59E2B1BB5C9C0A086
#include "fileloader.h"
#include "enums.h"
class Creature;
class Player;
class PropStream;
enum ConditionAttr_t {
CONDITIONATTR_TYPE = 1,
CONDITIONATTR_ID,
CONDITIONATTR_TICKS,
CONDITIONATTR_HEALTHTICKS,
CONDITIONATTR_HEALTHGAIN,
CONDITIONATTR_MANATICKS,
CONDITIONATTR_MANAGAIN,
CONDITIONATTR_OWNER,
CONDITIONATTR_CYCLE,
CONDITIONATTR_COUNT,
CONDITIONATTR_MAX_COUNT,
CONDITIONATTR_FACTOR_PERCENT,
CONDITIONATTR_SPEEDDELTA,
CONDITIONATTR_APPLIEDSPEEDDELTA,
CONDITIONATTR_FORMULA_MINA,
CONDITIONATTR_FORMULA_MINB,
CONDITIONATTR_FORMULA_MAXA,
CONDITIONATTR_FORMULA_MAXB,
CONDITIONATTR_LIGHTCOLOR,
CONDITIONATTR_LIGHTLEVEL,
CONDITIONATTR_LIGHTTICKS,
CONDITIONATTR_LIGHTINTERVAL,
CONDITIONATTR_SOULTICKS,
CONDITIONATTR_SOULGAIN,
CONDITIONATTR_SKILLS,
CONDITIONATTR_STATS,
CONDITIONATTR_OUTFIT,
CONDITIONATTR_SUBID,
//reserved for serialization
CONDITIONATTR_END = 254,
};
struct IntervalInfo {
int32_t timeLeft;
int32_t value;
int32_t interval;
};
class Condition
{
public:
Condition() = default;
Condition(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) :
endTime(ticks == -1 ? std::numeric_limits<int64_t>::max() : 0),
subId(subId), ticks(ticks), conditionType(type), id(id) {}
virtual ~Condition() = default;
virtual bool startCondition(Creature* creature);
virtual bool executeCondition(Creature* creature, int32_t interval);
virtual void endCondition(Creature* creature) = 0;
virtual void addCondition(Creature* creature, const Condition* condition) = 0;
virtual uint32_t getIcons() const;
ConditionId_t getId() const {
return id;
}
uint32_t getSubId() const {
return subId;
}
virtual Condition* clone() const = 0;
ConditionType_t getType() const {
return conditionType;
}
int64_t getEndTime() const {
return endTime;
}
int32_t getTicks() const {
return ticks;
}
void setTicks(int32_t newTicks);
static Condition* createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param = 0, uint32_t subId = 0);
static Condition* createCondition(PropStream& propStream);
virtual bool setParam(ConditionParam_t param, int32_t value);
//serialization
bool unserialize(PropStream& propStream);
virtual void serialize(PropWriteStream& propWriteStream);
virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream);
bool isPersistent() const;
protected:
int64_t endTime;
uint32_t subId;
int32_t ticks;
ConditionType_t conditionType;
ConditionId_t id;
virtual bool updateCondition(const Condition* addCondition);
};
class ConditionGeneric : public Condition
{
public:
ConditionGeneric(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0):
Condition(id, type, ticks, subId) {}
bool startCondition(Creature* creature) override;
bool executeCondition(Creature* creature, int32_t interval) override;
void endCondition(Creature* creature) override;
void addCondition(Creature* creature, const Condition* condition) override;
uint32_t getIcons() const override;
ConditionGeneric* clone() const override {
return new ConditionGeneric(*this);
}
};
class ConditionAttributes final : public ConditionGeneric
{
public:
ConditionAttributes(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) :
ConditionGeneric(id, type, ticks, subId) {}
bool startCondition(Creature* creature) final;
bool executeCondition(Creature* creature, int32_t interval) final;
void endCondition(Creature* creature) final;
void addCondition(Creature* creature, const Condition* condition) final;
bool setParam(ConditionParam_t param, int32_t value) final;
ConditionAttributes* clone() const final {
return new ConditionAttributes(*this);
}
//serialization
void serialize(PropWriteStream& propWriteStream) final;
bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final;
protected:
int32_t skills[SKILL_LAST + 1] = {};
int32_t skillsPercent[SKILL_LAST + 1] = {};
int32_t stats[STAT_LAST + 1] = {};
int32_t statsPercent[STAT_LAST + 1] = {};
int32_t currentSkill = 0;
int32_t currentStat = 0;
void updatePercentStats(Player* player);
void updateStats(Player* player);
void updatePercentSkills(Player* player);
void updateSkills(Player* player);
};
class ConditionRegeneration final : public ConditionGeneric
{
public:
ConditionRegeneration(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0):
ConditionGeneric(id, type, ticks, subId) {}
void addCondition(Creature* creature, const Condition* addCondition) final;
bool executeCondition(Creature* creature, int32_t interval) final;
bool setParam(ConditionParam_t param, int32_t value) final;
ConditionRegeneration* clone() const final {
return new ConditionRegeneration(*this);
}
//serialization
void serialize(PropWriteStream& propWriteStream) final;
bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final;
protected:
uint32_t internalHealthTicks = 0;
uint32_t internalManaTicks = 0;
uint32_t healthTicks = 1000;
uint32_t manaTicks = 1000;
uint32_t healthGain = 0;
uint32_t manaGain = 0;
};
class ConditionSoul final : public ConditionGeneric
{
public:
ConditionSoul(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) :
ConditionGeneric(id, type, ticks, subId) {}
void addCondition(Creature* creature, const Condition* addCondition) final;
bool executeCondition(Creature* creature, int32_t interval) final;
bool setParam(ConditionParam_t param, int32_t value) final;
ConditionSoul* clone() const final {
return new ConditionSoul(*this);
}
//serialization
void serialize(PropWriteStream& propWriteStream) final;
bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final;
protected:
uint32_t internalSoulTicks = 0;
uint32_t soulTicks = 0;
uint32_t soulGain = 0;
};
class ConditionInvisible final : public ConditionGeneric
{
public:
ConditionInvisible(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) :
ConditionGeneric(id, type, ticks, subId) {}
bool startCondition(Creature* creature) final;
void endCondition(Creature* creature) final;
ConditionInvisible* clone() const final {
return new ConditionInvisible(*this);
}
};
class ConditionDamage final : public Condition
{
public:
ConditionDamage() = default;
ConditionDamage(ConditionId_t id, ConditionType_t type, uint32_t subId = 0) :
Condition(id, type, 0, subId) {
if (type == CONDITION_POISON) {
count = max_count = 3;
} else if (type == CONDITION_FIRE) {
count = max_count = 8;
} else if (type == CONDITION_ENERGY) {
count = max_count = 10;
}
}
bool startCondition(Creature* creature) final;
bool executeCondition(Creature* creature, int32_t interval) final;
void endCondition(Creature* creature) final;
void addCondition(Creature* creature, const Condition* condition) final;
uint32_t getIcons() const final;
ConditionDamage* clone() const final {
return new ConditionDamage(*this);
}
bool setParam(ConditionParam_t param, int32_t value) final;
int32_t getTotalDamage() const;
//serialization
void serialize(PropWriteStream& propWriteStream) final;
bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final;
protected:
int32_t cycle = 0;
int32_t count = 0;
int32_t max_count = 0;
int32_t factor_percent = -1;
int32_t hit_damage = 0;
uint32_t owner = 0;
bool doDamage(Creature* creature, int32_t healthChange);
bool updateCondition(const Condition* addCondition) final;
};
class ConditionSpeed final : public Condition
{
public:
ConditionSpeed(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId, int32_t changeSpeed) :
Condition(id, type, ticks, subId), speedDelta(changeSpeed) {}
bool startCondition(Creature* creature) final;
bool executeCondition(Creature* creature, int32_t interval) final;
void endCondition(Creature* creature) final;
void addCondition(Creature* creature, const Condition* condition) final;
uint32_t getIcons() const final;
ConditionSpeed* clone() const final {
return new ConditionSpeed(*this);
}
void setVariation(int32_t newVariation) {
variation = newVariation;
}
void setSpeedDelta(int32_t newSpeedDelta) {
speedDelta = newSpeedDelta;
}
//serialization
void serialize(PropWriteStream& propWriteStream) final;
bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final;
protected:
int32_t appliedSpeedDelta = 0;
int32_t speedDelta = 0;
int32_t variation = 0;
};
class ConditionOutfit final : public Condition
{
public:
ConditionOutfit(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) :
Condition(id, type, ticks, subId) {}
bool startCondition(Creature* creature) final;
bool executeCondition(Creature* creature, int32_t interval) final;
void endCondition(Creature* creature) final;
void addCondition(Creature* creature, const Condition* condition) final;
ConditionOutfit* clone() const final {
return new ConditionOutfit(*this);
}
void setOutfit(const Outfit_t& outfit);
//serialization
void serialize(PropWriteStream& propWriteStream) final;
bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final;
protected:
Outfit_t outfit;
};
class ConditionLight final : public Condition
{
public:
ConditionLight(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId, uint8_t lightlevel, uint8_t lightcolor) :
Condition(id, type, ticks, subId), lightInfo(lightlevel, lightcolor) {}
bool startCondition(Creature* creature) final;
bool executeCondition(Creature* creature, int32_t interval) final;
void endCondition(Creature* creature) final;
void addCondition(Creature* creature, const Condition* addCondition) final;
ConditionLight* clone() const final {
return new ConditionLight(*this);
}
bool setParam(ConditionParam_t param, int32_t value) final;
//serialization
void serialize(PropWriteStream& propWriteStream) final;
bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final;
protected:
LightInfo lightInfo;
uint32_t internalLightTicks = 0;
uint32_t lightChangeInterval = 0;
};
#endif

205
src/configmanager.cpp Normal file
View File

@@ -0,0 +1,205 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "configmanager.h"
#include "game.h"
#if LUA_VERSION_NUM >= 502
#undef lua_strlen
#define lua_strlen lua_rawlen
#endif
extern Game g_game;
bool ConfigManager::load()
{
lua_State* L = luaL_newstate();
if (!L) {
throw std::runtime_error("Failed to allocate memory");
}
luaL_openlibs(L);
if (luaL_dofile(L, "config.lua")) {
std::cout << "[Error - ConfigManager::load] " << lua_tostring(L, -1) << std::endl;
lua_close(L);
return false;
}
//parse config
if (!loaded) { //info that must be loaded one time (unless we reset the modules involved)
boolean[BIND_ONLY_GLOBAL_ADDRESS] = getGlobalBoolean(L, "bindOnlyGlobalAddress", false);
boolean[OPTIMIZE_DATABASE] = getGlobalBoolean(L, "startupDatabaseOptimization", true);
string[IP] = getGlobalString(L, "ip", "127.0.0.1");
string[MAP_NAME] = getGlobalString(L, "mapName", "forgotten");
string[MAP_AUTHOR] = getGlobalString(L, "mapAuthor", "Unknown");
string[HOUSE_RENT_PERIOD] = getGlobalString(L, "houseRentPeriod", "never");
string[MYSQL_HOST] = getGlobalString(L, "mysqlHost", "127.0.0.1");
string[MYSQL_USER] = getGlobalString(L, "mysqlUser", "forgottenserver");
string[MYSQL_PASS] = getGlobalString(L, "mysqlPass", "");
string[MYSQL_DB] = getGlobalString(L, "mysqlDatabase", "forgottenserver");
string[MYSQL_SOCK] = getGlobalString(L, "mysqlSock", "");
integer[SQL_PORT] = getGlobalNumber(L, "mysqlPort", 3306);
integer[GAME_PORT] = getGlobalNumber(L, "gameProtocolPort", 7172);
integer[LOGIN_PORT] = getGlobalNumber(L, "loginProtocolPort", 7171);
integer[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171);
}
boolean[SHOW_MONSTER_LOOT] = getGlobalBoolean(L, "showMonsterLoot", true);
boolean[ALLOW_CHANGEOUTFIT] = getGlobalBoolean(L, "allowChangeOutfit", true);
boolean[ONE_PLAYER_ON_ACCOUNT] = getGlobalBoolean(L, "onePlayerOnlinePerAccount", true);
boolean[REMOVE_RUNE_CHARGES] = getGlobalBoolean(L, "removeChargesFromRunes", true);
boolean[EXPERIENCE_FROM_PLAYERS] = getGlobalBoolean(L, "experienceByKillingPlayers", false);
boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false);
boolean[REPLACE_KICK_ON_LOGIN] = getGlobalBoolean(L, "replaceKickOnLogin", true);
boolean[ALLOW_CLONES] = getGlobalBoolean(L, "allowClones", false);
boolean[WARN_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "warnUnsafeScripts", true);
boolean[CONVERT_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "convertUnsafeScripts", true);
boolean[TELEPORT_NEWBIES] = getGlobalBoolean(L, "teleportNewbies", true);
boolean[STACK_CUMULATIVES] = getGlobalBoolean(L, "autoStackCumulatives", false);
string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high");
string[SERVER_NAME] = getGlobalString(L, "serverName", "");
string[OWNER_NAME] = getGlobalString(L, "ownerName", "");
string[OWNER_EMAIL] = getGlobalString(L, "ownerEmail", "");
string[URL] = getGlobalString(L, "url", "");
string[LOCATION] = getGlobalString(L, "location", "");
string[MOTD] = getGlobalString(L, "motd", "");
string[WORLD_TYPE] = getGlobalString(L, "worldType", "pvp");
integer[MAX_PLAYERS] = getGlobalNumber(L, "maxPlayers");
integer[PZ_LOCKED] = getGlobalNumber(L, "pzLocked", 60000);
integer[DEFAULT_DESPAWNRANGE] = getGlobalNumber(L, "deSpawnRange", 2);
integer[DEFAULT_DESPAWNRADIUS] = getGlobalNumber(L, "deSpawnRadius", 50);
integer[RATE_EXPERIENCE] = getGlobalNumber(L, "rateExp", 5);
integer[RATE_SKILL] = getGlobalNumber(L, "rateSkill", 3);
integer[RATE_LOOT] = getGlobalNumber(L, "rateLoot", 2);
integer[RATE_MAGIC] = getGlobalNumber(L, "rateMagic", 3);
integer[RATE_SPAWN] = getGlobalNumber(L, "rateSpawn", 1);
integer[BAN_LENGTH] = getGlobalNumber(L, "banLength", 30 * 24 * 60 * 60);
integer[ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenActions", 200);
integer[EX_ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenExActions", 1000);
integer[MAX_MESSAGEBUFFER] = getGlobalNumber(L, "maxMessageBuffer", 4);
integer[KICK_AFTER_MINUTES] = getGlobalNumber(L, "kickIdlePlayerAfterMinutes", 15);
integer[PROTECTION_LEVEL] = getGlobalNumber(L, "protectionLevel", 1);
integer[DEATH_LOSE_PERCENT] = getGlobalNumber(L, "deathLosePercent", -1);
integer[STATUSQUERY_TIMEOUT] = getGlobalNumber(L, "statusTimeout", 5000);
integer[WHITE_SKULL_TIME] = getGlobalNumber(L, "whiteSkullTime", 15 * 60);
integer[RED_SKULL_TIME] = getGlobalNumber(L, "redSkullTime", 30 * 24 * 60 * 60);
integer[KILLS_DAY_RED_SKULL] = getGlobalNumber(L, "killsDayRedSkull", 3);
integer[KILLS_WEEK_RED_SKULL] = getGlobalNumber(L, "killsWeekRedSkull", 5);
integer[KILLS_MONTH_RED_SKULL] = getGlobalNumber(L, "killsMonthRedSkull", 10);
integer[KILLS_DAY_BANISHMENT] = getGlobalNumber(L, "killsDayBanishment", 5);
integer[KILLS_WEEK_BANISHMENT] = getGlobalNumber(L, "killsWeekBanishment", 8);
integer[KILLS_MONTH_BANISHMENT] = getGlobalNumber(L, "killsMonthBanishment", 10);
integer[STAIRHOP_DELAY] = getGlobalNumber(L, "stairJumpExhaustion", 2000);
integer[EXP_FROM_PLAYERS_LEVEL_RANGE] = getGlobalNumber(L, "expFromPlayersLevelRange", 75);
integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25);
integer[NEWBIE_TOWN] = getGlobalNumber(L, "newbieTownId", 1);
integer[NEWBIE_LEVEL_THRESHOLD] = getGlobalNumber(L, "newbieLevelThreshold", 5);
integer[MONEY_RATE] = getGlobalNumber(L, "moneyRate", 1);
loaded = true;
lua_close(L);
return true;
}
bool ConfigManager::reload()
{
bool result = load();
if (transformToSHA1(getString(ConfigManager::MOTD)) != g_game.getMotdHash()) {
g_game.incrementMotdNum();
}
return result;
}
const std::string& ConfigManager::getString(string_config_t what) const
{
if (what >= LAST_STRING_CONFIG) {
std::cout << "[Warning - ConfigManager::getString] Accessing invalid index: " << what << std::endl;
return string[DUMMY_STR];
}
return string[what];
}
int32_t ConfigManager::getNumber(integer_config_t what) const
{
if (what >= LAST_INTEGER_CONFIG) {
std::cout << "[Warning - ConfigManager::getNumber] Accessing invalid index: " << what << std::endl;
return 0;
}
return integer[what];
}
bool ConfigManager::getBoolean(boolean_config_t what) const
{
if (what >= LAST_BOOLEAN_CONFIG) {
std::cout << "[Warning - ConfigManager::getBoolean] Accessing invalid index: " << what << std::endl;
return false;
}
return boolean[what];
}
std::string ConfigManager::getGlobalString(lua_State* L, const char* identifier, const char* defaultValue)
{
lua_getglobal(L, identifier);
if (!lua_isstring(L, -1)) {
return defaultValue;
}
size_t len = lua_strlen(L, -1);
std::string ret(lua_tostring(L, -1), len);
lua_pop(L, 1);
return ret;
}
int32_t ConfigManager::getGlobalNumber(lua_State* L, const char* identifier, const int32_t defaultValue)
{
lua_getglobal(L, identifier);
if (!lua_isnumber(L, -1)) {
return defaultValue;
}
int32_t val = lua_tonumber(L, -1);
lua_pop(L, 1);
return val;
}
bool ConfigManager::getGlobalBoolean(lua_State* L, const char* identifier, const bool defaultValue)
{
lua_getglobal(L, identifier);
if (!lua_isboolean(L, -1)) {
if (!lua_isstring(L, -1)) {
return defaultValue;
}
size_t len = lua_strlen(L, -1);
std::string ret(lua_tostring(L, -1), len);
lua_pop(L, 1);
return booleanString(ret);
}
int val = lua_toboolean(L, -1);
lua_pop(L, 1);
return val != 0;
}

129
src/configmanager.h Normal file
View File

@@ -0,0 +1,129 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39
#define FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39
#include <lua.hpp>
class ConfigManager
{
public:
enum boolean_config_t {
SHOW_MONSTER_LOOT,
ALLOW_CHANGEOUTFIT,
ONE_PLAYER_ON_ACCOUNT,
REMOVE_RUNE_CHARGES,
EXPERIENCE_FROM_PLAYERS,
FREE_PREMIUM,
REPLACE_KICK_ON_LOGIN,
ALLOW_CLONES,
BIND_ONLY_GLOBAL_ADDRESS,
OPTIMIZE_DATABASE,
WARN_UNSAFE_SCRIPTS,
CONVERT_UNSAFE_SCRIPTS,
TELEPORT_NEWBIES,
STACK_CUMULATIVES,
LAST_BOOLEAN_CONFIG /* this must be the last one */
};
enum string_config_t {
DUMMY_STR,
MAP_NAME,
HOUSE_RENT_PERIOD,
SERVER_NAME,
OWNER_NAME,
OWNER_EMAIL,
URL,
LOCATION,
IP,
MOTD,
WORLD_TYPE,
MYSQL_HOST,
MYSQL_USER,
MYSQL_PASS,
MYSQL_DB,
MYSQL_SOCK,
DEFAULT_PRIORITY,
MAP_AUTHOR,
LAST_STRING_CONFIG /* this must be the last one */
};
enum integer_config_t {
SQL_PORT,
MAX_PLAYERS,
PZ_LOCKED,
DEFAULT_DESPAWNRANGE,
DEFAULT_DESPAWNRADIUS,
RATE_EXPERIENCE,
RATE_SKILL,
RATE_LOOT,
RATE_MAGIC,
RATE_SPAWN,
BAN_LENGTH,
MAX_MESSAGEBUFFER,
ACTIONS_DELAY_INTERVAL,
EX_ACTIONS_DELAY_INTERVAL,
KICK_AFTER_MINUTES,
PROTECTION_LEVEL,
DEATH_LOSE_PERCENT,
STATUSQUERY_TIMEOUT,
WHITE_SKULL_TIME,
RED_SKULL_TIME,
KILLS_DAY_RED_SKULL,
KILLS_WEEK_RED_SKULL,
KILLS_MONTH_RED_SKULL,
KILLS_DAY_BANISHMENT,
KILLS_WEEK_BANISHMENT,
KILLS_MONTH_BANISHMENT,
GAME_PORT,
LOGIN_PORT,
STATUS_PORT,
STAIRHOP_DELAY,
EXP_FROM_PLAYERS_LEVEL_RANGE,
MAX_PACKETS_PER_SECOND,
NEWBIE_TOWN,
NEWBIE_LEVEL_THRESHOLD,
MONEY_RATE,
LAST_INTEGER_CONFIG /* this must be the last one */
};
bool load();
bool reload();
const std::string& getString(string_config_t what) const;
int32_t getNumber(integer_config_t what) const;
bool getBoolean(boolean_config_t what) const;
private:
static std::string getGlobalString(lua_State* L, const char* identifier, const char* defaultValue);
static int32_t getGlobalNumber(lua_State* L, const char* identifier, const int32_t defaultValue = 0);
static bool getGlobalBoolean(lua_State* L, const char* identifier, const bool defaultValue);
std::string string[LAST_STRING_CONFIG] = {};
int32_t integer[LAST_INTEGER_CONFIG] = {};
bool boolean[LAST_BOOLEAN_CONFIG] = {};
bool loaded = false;
};
#endif

298
src/connection.cpp Normal file
View File

@@ -0,0 +1,298 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "configmanager.h"
#include "connection.h"
#include "outputmessage.h"
#include "protocol.h"
#include "scheduler.h"
#include "server.h"
extern ConfigManager g_config;
Connection_ptr ConnectionManager::createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort)
{
std::lock_guard<std::mutex> lockClass(connectionManagerLock);
auto connection = std::make_shared<Connection>(io_service, servicePort);
connections.insert(connection);
return connection;
}
void ConnectionManager::releaseConnection(const Connection_ptr& connection)
{
std::lock_guard<std::mutex> lockClass(connectionManagerLock);
connections.erase(connection);
}
void ConnectionManager::closeAll()
{
std::lock_guard<std::mutex> lockClass(connectionManagerLock);
for (const auto& connection : connections) {
try {
boost::system::error_code error;
connection->socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
connection->socket.close(error);
} catch (boost::system::system_error&) {
}
}
connections.clear();
}
// Connection
void Connection::close(bool force)
{
//any thread
ConnectionManager::getInstance().releaseConnection(shared_from_this());
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
if (connectionState != CONNECTION_STATE_OPEN) {
return;
}
connectionState = CONNECTION_STATE_CLOSED;
if (protocol) {
g_dispatcher.addTask(
createTask(std::bind(&Protocol::release, protocol)));
}
if (messageQueue.empty() || force) {
closeSocket();
} else {
//will be closed by the destructor or onWriteOperation
}
}
void Connection::closeSocket()
{
if (socket.is_open()) {
try {
readTimer.cancel();
writeTimer.cancel();
boost::system::error_code error;
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
socket.close(error);
} catch (boost::system::system_error& e) {
std::cout << "[Network error - Connection::closeSocket] " << e.what() << std::endl;
}
}
}
Connection::~Connection()
{
closeSocket();
}
void Connection::accept(Protocol_ptr protocol)
{
this->protocol = protocol;
g_dispatcher.addTask(createTask(std::bind(&Protocol::onConnect, protocol)));
accept();
}
void Connection::accept()
{
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
try {
readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT));
readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()), std::placeholders::_1));
// Read size of the first packet
boost::asio::async_read(socket,
boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
} catch (boost::system::system_error& e) {
std::cout << "[Network error - Connection::accept] " << e.what() << std::endl;
close(FORCE_CLOSE);
}
}
void Connection::parseHeader(const boost::system::error_code& error)
{
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
readTimer.cancel();
if (error) {
close(FORCE_CLOSE);
return;
} else if (connectionState != CONNECTION_STATE_OPEN) {
return;
}
uint32_t timePassed = std::max<uint32_t>(1, (time(nullptr) - timeConnected) + 1);
if ((++packetsSent / timePassed) > static_cast<uint32_t>(g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND))) {
std::cout << convertIPToString(getIP()) << " disconnected for exceeding packet per second limit." << std::endl;
close();
return;
}
if (timePassed > 2) {
timeConnected = time(nullptr);
packetsSent = 0;
}
uint16_t size = msg.getLengthHeader();
if (size == 0 || size >= NETWORKMESSAGE_MAXSIZE - 16) {
close(FORCE_CLOSE);
return;
}
try {
readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT));
readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
std::placeholders::_1));
// Read packet content
msg.setLength(size + NetworkMessage::HEADER_LENGTH);
boost::asio::async_read(socket, boost::asio::buffer(msg.getBodyBuffer(), size),
std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1));
} catch (boost::system::system_error& e) {
std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl;
close(FORCE_CLOSE);
}
}
void Connection::parsePacket(const boost::system::error_code& error)
{
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
readTimer.cancel();
if (error) {
close(FORCE_CLOSE);
return;
} else if (connectionState != CONNECTION_STATE_OPEN) {
return;
}
if (!receivedFirst) {
// First message received
receivedFirst = true;
if (!protocol) {
// Game protocol has already been created at this point
protocol = service_port->make_protocol(msg, shared_from_this());
if (!protocol) {
close(FORCE_CLOSE);
return;
}
} else {
msg.skipBytes(1); // Skip protocol ID
}
protocol->onRecvFirstMessage(msg);
} else {
protocol->onRecvMessage(msg); // Send the packet to the current protocol
}
try {
readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT));
readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
std::placeholders::_1));
// Wait to the next packet
boost::asio::async_read(socket,
boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
} catch (boost::system::system_error& e) {
std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl;
close(FORCE_CLOSE);
}
}
void Connection::send(const OutputMessage_ptr& msg)
{
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
if (connectionState != CONNECTION_STATE_OPEN) {
return;
}
bool noPendingWrite = messageQueue.empty();
messageQueue.emplace_back(msg);
if (noPendingWrite) {
internalSend(msg);
}
}
void Connection::internalSend(const OutputMessage_ptr& msg)
{
protocol->onSendMessage(msg);
try {
writeTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_WRITE_TIMEOUT));
writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
std::placeholders::_1));
boost::asio::async_write(socket,
boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()),
std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1));
} catch (boost::system::system_error& e) {
std::cout << "[Network error - Connection::internalSend] " << e.what() << std::endl;
close(FORCE_CLOSE);
}
}
uint32_t Connection::getIP()
{
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
// IP-address is expressed in network byte order
boost::system::error_code error;
const boost::asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error);
if (error) {
return 0;
}
return htonl(endpoint.address().to_v4().to_ulong());
}
void Connection::onWriteOperation(const boost::system::error_code& error)
{
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
writeTimer.cancel();
messageQueue.pop_front();
if (error) {
messageQueue.clear();
close(FORCE_CLOSE);
return;
}
if (!messageQueue.empty()) {
internalSend(messageQueue.front());
} else if (connectionState == CONNECTION_STATE_CLOSED) {
closeSocket();
}
}
void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error)
{
if (error == boost::asio::error::operation_aborted) {
//The timer has been manually cancelled
return;
}
if (auto connection = connectionWeak.lock()) {
connection->close(FORCE_CLOSE);
}
}

137
src/connection.h Normal file
View File

@@ -0,0 +1,137 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348
#define FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348
#include <unordered_set>
#include "networkmessage.h"
static constexpr int32_t CONNECTION_WRITE_TIMEOUT = 30;
static constexpr int32_t CONNECTION_READ_TIMEOUT = 30;
class Protocol;
typedef std::shared_ptr<Protocol> Protocol_ptr;
class OutputMessage;
typedef std::shared_ptr<OutputMessage> OutputMessage_ptr;
class Connection;
typedef std::shared_ptr<Connection> Connection_ptr;
typedef std::weak_ptr<Connection> ConnectionWeak_ptr;
class ServiceBase;
typedef std::shared_ptr<ServiceBase> Service_ptr;
class ServicePort;
typedef std::shared_ptr<ServicePort> ServicePort_ptr;
typedef std::shared_ptr<const ServicePort> ConstServicePort_ptr;
class ConnectionManager
{
public:
static ConnectionManager& getInstance() {
static ConnectionManager instance;
return instance;
}
Connection_ptr createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort);
void releaseConnection(const Connection_ptr& connection);
void closeAll();
protected:
ConnectionManager() = default;
std::unordered_set<Connection_ptr> connections;
std::mutex connectionManagerLock;
};
class Connection : public std::enable_shared_from_this<Connection>
{
public:
// non-copyable
Connection(const Connection&) = delete;
Connection& operator=(const Connection&) = delete;
enum ConnectionState_t {
CONNECTION_STATE_OPEN,
CONNECTION_STATE_CLOSED,
};
enum { FORCE_CLOSE = true };
Connection(boost::asio::io_service& io_service,
ConstServicePort_ptr service_port) :
readTimer(io_service),
writeTimer(io_service),
service_port(std::move(service_port)),
socket(io_service) {
connectionState = CONNECTION_STATE_OPEN;
receivedFirst = false;
packetsSent = 0;
timeConnected = time(nullptr);
}
~Connection();
friend class ConnectionManager;
void close(bool force = false);
// Used by protocols that require server to send first
void accept(Protocol_ptr protocol);
void accept();
void send(const OutputMessage_ptr& msg);
uint32_t getIP();
private:
void parseHeader(const boost::system::error_code& error);
void parsePacket(const boost::system::error_code& error);
void onWriteOperation(const boost::system::error_code& error);
static void handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error);
void closeSocket();
void internalSend(const OutputMessage_ptr& msg);
boost::asio::ip::tcp::socket& getSocket() {
return socket;
}
friend class ServicePort;
NetworkMessage msg;
boost::asio::deadline_timer readTimer;
boost::asio::deadline_timer writeTimer;
std::recursive_mutex connectionLock;
std::list<OutputMessage_ptr> messageQueue;
ConstServicePort_ptr service_port;
Protocol_ptr protocol;
boost::asio::ip::tcp::socket socket;
time_t timeConnected;
uint32_t packetsSent;
bool connectionState;
bool receivedFirst;
};
#endif

316
src/const.h Normal file
View File

@@ -0,0 +1,316 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
#define FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590;
enum MagicEffectClasses : uint8_t {
CONST_ME_NONE,
CONST_ME_DRAWBLOOD = 1,
CONST_ME_LOSEENERGY = 2,
CONST_ME_POFF = 3,
CONST_ME_BLOCKHIT = 4,
CONST_ME_EXPLOSIONAREA = 5,
CONST_ME_EXPLOSIONHIT = 6,
CONST_ME_FIREAREA = 7,
CONST_ME_YELLOW_RINGS = 8,
CONST_ME_GREEN_RINGS = 9,
CONST_ME_HITAREA = 10,
CONST_ME_TELEPORT = 11,
CONST_ME_ENERGYHIT = 12,
CONST_ME_MAGIC_BLUE = 13,
CONST_ME_MAGIC_RED = 14,
CONST_ME_MAGIC_GREEN = 15,
CONST_ME_HITBYFIRE = 16,
CONST_ME_HITBYPOISON = 17,
CONST_ME_MORTAREA = 18,
CONST_ME_SOUND_GREEN = 19,
CONST_ME_SOUND_RED = 20,
CONST_ME_POISONAREA = 21,
CONST_ME_SOUND_YELLOW = 22,
CONST_ME_SOUND_PURPLE = 23,
CONST_ME_SOUND_BLUE = 24,
CONST_ME_SOUND_WHITE = 25,
};
enum ShootType_t : uint8_t {
CONST_ANI_NONE,
CONST_ANI_SPEAR = 1,
CONST_ANI_BOLT = 2,
CONST_ANI_ARROW = 3,
CONST_ANI_FIRE = 4,
CONST_ANI_ENERGY = 5,
CONST_ANI_POISONARROW = 6,
CONST_ANI_BURSTARROW = 7,
CONST_ANI_THROWINGSTAR = 8,
CONST_ANI_THROWINGKNIFE = 9,
CONST_ANI_SMALLSTONE = 10,
CONST_ANI_DEATH = 11,
CONST_ANI_LARGEROCK = 12,
CONST_ANI_SNOWBALL = 13,
CONST_ANI_POWERBOLT = 14,
CONST_ANI_POISON = 15,
};
enum SpeakClasses : uint8_t {
TALKTYPE_SAY = 1,
TALKTYPE_WHISPER = 2,
TALKTYPE_YELL = 3,
TALKTYPE_PRIVATE = 4,
TALKTYPE_CHANNEL_Y = 5, // Yellow
TALKTYPE_RVR_CHANNEL = 6,
TALKTYPE_RVR_ANSWER = 7,
TALKTYPE_RVR_CONTINUE = 8,
TALKTYPE_BROADCAST = 9,
TALKTYPE_CHANNEL_R1 = 10, // Red - #c text
TALKTYPE_PRIVATE_RED = 11, // @name@text
TALKTYPE_CHANNEL_O = 12, // orange
TALKTYPE_CHANNEL_R2 = 13, // red anonymous - #d text
TALKTYPE_MONSTER_YELL = 0x10,
TALKTYPE_MONSTER_SAY = 0x11,
};
enum MessageClasses : uint8_t {
MESSAGE_STATUS_CONSOLE_YELLOW = 0x01, //Yellow message in the console
MESSAGE_STATUS_CONSOLE_LBLUE = 0x04, //Light blue message in the console
MESSAGE_STATUS_CONSOLE_ORANGE = 0x11, //Orange message in the console
MESSAGE_STATUS_WARNING = 0x12, //Red message in game window and in the console
MESSAGE_EVENT_ADVANCE = 0x13, //White message in game window and in the console
MESSAGE_EVENT_DEFAULT = 0x14, //White message at the bottom of the game window and in the console
MESSAGE_STATUS_DEFAULT = 0x15, //White message at the bottom of the game window and in the console
MESSAGE_INFO_DESCR = 0x16, //Green message in game window and in the console
MESSAGE_STATUS_SMALL = 0x17, //White message at the bottom of the game window"
MESSAGE_STATUS_CONSOLE_BLUE = 0x18, //Blue message in the console
MESSAGE_STATUS_CONSOLE_RED = 0x19, //Red message in the console
MESSAGE_CLASS_FIRST = MESSAGE_STATUS_CONSOLE_YELLOW,
MESSAGE_CLASS_LAST = MESSAGE_STATUS_CONSOLE_RED,
};
enum FluidTypes_t : uint8_t
{
FLUID_NONE = 0,
FLUID_WATER,
FLUID_WINE,
FLUID_BEER,
FLUID_MUD,
FLUID_BLOOD,
FLUID_SLIME,
FLUID_OIL,
FLUID_URINE,
FLUID_MILK,
FLUID_MANAFLUID,
FLUID_LIFEFLUID,
FLUID_LEMONADE,
};
enum FluidColor_t : uint8_t
{
FLUID_COLOR_NONE = 0,
FLUID_COLOR_BLUE = 1,
FLUID_COLOR_PURPLE = 2,
FLUID_COLOR_BROWN = 3,
FLUID_COLOR_RED = 4,
FLUID_COLOR_GREEN = 5,
FLUID_COLOR_YELLOW = 6,
FLUID_COLOR_WHITE = 7,
};
enum SquareColor_t : uint8_t {
SQ_COLOR_BLACK = 0,
};
enum TextColor_t : uint8_t {
TEXTCOLOR_BLUE = 5,
TEXTCOLOR_LIGHTGREEN = 30,
TEXTCOLOR_LIGHTBLUE = 35,
TEXTCOLOR_MAYABLUE = 95,
TEXTCOLOR_DARKRED = 108,
TEXTCOLOR_LIGHTGREY = 129,
TEXTCOLOR_SKYBLUE = 143,
TEXTCOLOR_PURPLE = 155,
TEXTCOLOR_RED = 180,
TEXTCOLOR_ORANGE = 198,
TEXTCOLOR_YELLOW = 210,
TEXTCOLOR_WHITE_EXP = 215,
TEXTCOLOR_NONE = 255,
};
enum Icons_t {
ICON_POISON = 1 << 0,
ICON_BURN = 1 << 1,
ICON_ENERGY = 1 << 2,
ICON_DRUNK = 1 << 3,
ICON_MANASHIELD = 1 << 4,
ICON_PARALYZE = 1 << 5,
ICON_HASTE = 1 << 6,
ICON_SWORDS = 1 << 7
};
enum WeaponType_t : uint8_t {
WEAPON_NONE,
WEAPON_SWORD,
WEAPON_CLUB,
WEAPON_AXE,
WEAPON_SHIELD,
WEAPON_DISTANCE,
WEAPON_WAND,
WEAPON_AMMO,
};
enum Ammo_t : uint8_t {
AMMO_NONE,
AMMO_BOLT,
AMMO_ARROW,
AMMO_SPEAR,
AMMO_THROWINGSTAR,
AMMO_THROWINGKNIFE,
AMMO_STONE,
AMMO_SNOWBALL,
};
enum WeaponAction_t : uint8_t {
WEAPONACTION_NONE,
WEAPONACTION_REMOVECOUNT,
WEAPONACTION_REMOVECHARGE,
WEAPONACTION_MOVE,
};
enum WieldInfo_t {
WIELDINFO_LEVEL = 1 << 0,
WIELDINFO_MAGLV = 1 << 1,
WIELDINFO_VOCREQ = 1 << 2,
WIELDINFO_PREMIUM = 1 << 3,
};
enum Skulls_t : uint8_t {
SKULL_NONE = 0,
SKULL_YELLOW = 1,
SKULL_GREEN = 2,
SKULL_WHITE = 3,
SKULL_RED = 4,
};
enum PartyShields_t : uint8_t {
SHIELD_NONE = 0,
SHIELD_WHITEYELLOW = 1,
SHIELD_WHITEBLUE = 2,
SHIELD_BLUE = 3,
SHIELD_YELLOW = 4
};
enum item_t : uint16_t {
ITEM_FIREFIELD_PVP_FULL = 2118,
ITEM_FIREFIELD_PVP_MEDIUM = 2119,
ITEM_FIREFIELD_PVP_SMALL = 2120,
ITEM_FIREFIELD_PERSISTENT_FULL = 2123,
ITEM_FIREFIELD_PERSISTENT_MEDIUM = 2124,
ITEM_FIREFIELD_PERSISTENT_SMALL = 2125,
ITEM_FIREFIELD_NOPVP = 2131,
ITEM_POISONFIELD_PVP = 2121,
ITEM_POISONFIELD_PERSISTENT = 2127,
ITEM_POISONFIELD_NOPVP = 2134,
ITEM_ENERGYFIELD_PVP = 2122,
ITEM_ENERGYFIELD_PERSISTENT = 2126,
ITEM_ENERGYFIELD_NOPVP = 2135,
ITEM_MAGICWALL = 2128,
ITEM_MAGICWALL_PERSISTENT = 2128,
ITEM_WILDGROWTH = 2130,
ITEM_WILDGROWTH_PERSISTENT = 2130,
ITEM_GOLD_COIN = 3031,
ITEM_PLATINUM_COIN = 3035,
ITEM_CRYSTAL_COIN = 3043,
ITEM_DEPOT = 3502,
ITEM_LOCKER1 = 3497,
ITEM_MALE_CORPSE = 4240,
ITEM_FEMALE_CORPSE = 4247,
ITEM_FULLSPLASH = 2886,
ITEM_SMALLSPLASH = 2889,
ITEM_PARCEL = 3503,
ITEM_PARCEL_STAMPED = 3504,
ITEM_LETTER = 3505,
ITEM_LETTER_STAMPED = 3506,
ITEM_LABEL = 3507,
ITEM_AMULETOFLOSS = 3057,
ITEM_DOCUMENT_RO = 2819, //read-only
};
enum PlayerFlags : uint64_t {
PlayerFlag_CannotUseCombat = 1 << 0,
PlayerFlag_CannotAttackPlayer = 1 << 1,
PlayerFlag_CannotAttackMonster = 1 << 2,
PlayerFlag_CannotBeAttacked = 1 << 3,
PlayerFlag_CanConvinceAll = 1 << 4,
PlayerFlag_CanSummonAll = 1 << 5,
PlayerFlag_CanIllusionAll = 1 << 6,
PlayerFlag_CanSenseInvisibility = 1 << 7,
PlayerFlag_IgnoredByMonsters = 1 << 8,
PlayerFlag_NotGainInFight = 1 << 9,
PlayerFlag_HasInfiniteMana = 1 << 10,
PlayerFlag_HasInfiniteSoul = 1 << 11,
PlayerFlag_HasNoExhaustion = 1 << 12,
PlayerFlag_CannotUseSpells = 1 << 13,
PlayerFlag_CannotPickupItem = 1 << 14,
PlayerFlag_CanAlwaysLogin = 1 << 15,
PlayerFlag_CanBroadcast = 1 << 16,
PlayerFlag_CanEditHouses = 1 << 17,
PlayerFlag_CannotBeBanned = 1 << 18,
PlayerFlag_CannotBePushed = 1 << 19,
PlayerFlag_HasInfiniteCapacity = 1 << 20,
PlayerFlag_CanPushAllCreatures = 1 << 21,
PlayerFlag_CanTalkRedPrivate = 1 << 22,
PlayerFlag_CanTalkRedChannel = 1 << 23,
PlayerFlag_TalkOrangeHelpChannel = 1 << 24,
PlayerFlag_NotGainExperience = 1 << 25,
PlayerFlag_NotGainMana = 1 << 26,
PlayerFlag_NotGainHealth = 1 << 27,
PlayerFlag_NotGainSkill = 1 << 28,
PlayerFlag_SetMaxSpeed = 1 << 29,
PlayerFlag_SpecialVIP = 1 << 30,
PlayerFlag_NotGenerateLoot = static_cast<uint64_t>(1) << 31,
PlayerFlag_CanTalkRedChannelAnonymous = static_cast<uint64_t>(1) << 32,
PlayerFlag_IgnoreProtectionZone = static_cast<uint64_t>(1) << 33,
PlayerFlag_IgnoreSpellCheck = static_cast<uint64_t>(1) << 34,
PlayerFlag_IgnoreWeaponCheck = static_cast<uint64_t>(1) << 35,
PlayerFlag_CannotBeMuted = static_cast<uint64_t>(1) << 36,
PlayerFlag_IsAlwaysPremium = static_cast<uint64_t>(1) << 37,
PlayerFlag_SpecialMoveUse = static_cast<uint64_t>(1) << 38,
};
static constexpr int32_t CHANNEL_GUILD = 0x00;
static constexpr int32_t CHANNEL_PARTY = 0x01;
static constexpr int32_t CHANNEL_RULE_REP = 0x02;
static constexpr int32_t CHANNEL_PRIVATE = 0xFFFF;
#endif

690
src/container.cpp Normal file
View File

@@ -0,0 +1,690 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "container.h"
#include "iomap.h"
#include "game.h"
extern Game g_game;
Container::Container(uint16_t type) :
Container(type, items[type].maxItems) {}
Container::Container(uint16_t type, uint16_t size) :
Item(type),
maxSize(size)
{}
Container::~Container()
{
for (Item* item : itemlist) {
item->setParent(nullptr);
item->decrementReferenceCounter();
}
}
Item* Container::clone() const
{
Container* clone = static_cast<Container*>(Item::clone());
for (Item* item : itemlist) {
clone->addItem(item->clone());
}
clone->totalWeight = totalWeight;
return clone;
}
Container* Container::getParentContainer()
{
Thing* thing = getParent();
if (!thing) {
return nullptr;
}
return thing->getContainer();
}
bool Container::hasParent() const
{
return dynamic_cast<const Container*>(getParent()) != nullptr;
}
void Container::addItem(Item* item)
{
itemlist.push_back(item);
item->setParent(this);
}
Attr_ReadValue Container::readAttr(AttrTypes_t attr, PropStream& propStream)
{
if (attr == ATTR_CONTAINER_ITEMS) {
if (!propStream.read<uint32_t>(serializationCount)) {
return ATTR_READ_ERROR;
}
return ATTR_READ_END;
}
return Item::readAttr(attr, propStream);
}
bool Container::unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream)
{
bool ret = Item::unserializeItemNode(f, node, propStream);
if (!ret) {
return false;
}
uint32_t type;
NODE nodeItem = f.getChildNode(node, type);
while (nodeItem) {
//load container items
if (type != OTBM_ITEM) {
// unknown type
return false;
}
PropStream itemPropStream;
if (!f.getProps(nodeItem, itemPropStream)) {
return false;
}
Item* item = Item::CreateItem(itemPropStream);
if (!item) {
return false;
}
if (!item->unserializeItemNode(f, nodeItem, itemPropStream)) {
return false;
}
addItem(item);
updateItemWeight(item->getWeight());
nodeItem = f.getNextNode(nodeItem, type);
}
return true;
}
void Container::updateItemWeight(int32_t diff)
{
totalWeight += diff;
if (Container* parentContainer = getParentContainer()) {
parentContainer->updateItemWeight(diff);
}
}
uint32_t Container::getWeight() const
{
return Item::getWeight() + totalWeight;
}
std::string Container::getContentDescription() const
{
std::ostringstream os;
return getContentDescription(os).str();
}
std::ostringstream& Container::getContentDescription(std::ostringstream& os) const
{
bool firstitem = true;
for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) {
Item* item = *it;
Container* container = item->getContainer();
if (container && !container->empty()) {
continue;
}
if (firstitem) {
firstitem = false;
} else {
os << ", ";
}
os << item->getNameDescription();
}
if (firstitem) {
os << "nothing";
}
return os;
}
Item* Container::getItemByIndex(size_t index) const
{
if (index >= size()) {
return nullptr;
}
return itemlist[index];
}
uint32_t Container::getItemHoldingCount() const
{
uint32_t counter = 0;
for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) {
++counter;
}
return counter;
}
bool Container::isHoldingItem(const Item* item) const
{
for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) {
if (*it == item) {
return true;
}
}
return false;
}
void Container::onAddContainerItem(Item* item)
{
SpectatorVec list;
g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2);
//send to client
for (Creature* spectator : list) {
spectator->getPlayer()->sendAddContainerItem(this, item);
}
//event methods
for (Creature* spectator : list) {
spectator->getPlayer()->onAddContainerItem(item);
}
}
void Container::onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem)
{
SpectatorVec list;
g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2);
//send to client
for (Creature* spectator : list) {
spectator->getPlayer()->sendUpdateContainerItem(this, index, newItem);
}
//event methods
for (Creature* spectator : list) {
spectator->getPlayer()->onUpdateContainerItem(this, oldItem, newItem);
}
}
void Container::onRemoveContainerItem(uint32_t index, Item* item)
{
SpectatorVec list;
g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2);
//send change to client
for (Creature* spectator : list) {
spectator->getPlayer()->sendRemoveContainerItem(this, index);
}
//event methods
for (Creature* spectator : list) {
spectator->getPlayer()->onRemoveContainerItem(this, item);
}
}
ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t count,
uint32_t flags, Creature* actor/* = nullptr*/) const
{
bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags);
if (childIsOwner) {
//a child container is querying, since we are the top container (not carried by a player)
//just return with no error.
return RETURNVALUE_NOERROR;
}
const Item* item = thing.getItem();
if (item == nullptr) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (!item->isPickupable()) {
return RETURNVALUE_CANNOTPICKUP;
}
if (item == this) {
return RETURNVALUE_THISISIMPOSSIBLE;
}
const Cylinder* cylinder = getParent();
if (!hasBitSet(FLAG_NOLIMIT, flags)) {
while (cylinder) {
if (cylinder == &thing) {
return RETURNVALUE_THISISIMPOSSIBLE;
}
cylinder = cylinder->getParent();
}
if (index == INDEX_WHEREEVER && size() >= capacity()) {
return RETURNVALUE_CONTAINERNOTENOUGHROOM;
}
} else {
while (cylinder) {
if (cylinder == &thing) {
return RETURNVALUE_THISISIMPOSSIBLE;
}
cylinder = cylinder->getParent();
}
}
const Cylinder* topParent = getTopParent();
if (topParent != this) {
return topParent->queryAdd(INDEX_WHEREEVER, *item, count, flags | FLAG_CHILDISOWNER, actor);
} else {
return RETURNVALUE_NOERROR;
}
}
ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t count,
uint32_t& maxQueryCount, uint32_t flags) const
{
const Item* item = thing.getItem();
if (item == nullptr) {
maxQueryCount = 0;
return RETURNVALUE_NOTPOSSIBLE;
}
if (hasBitSet(FLAG_NOLIMIT, flags)) {
maxQueryCount = std::max<uint32_t>(1, count);
return RETURNVALUE_NOERROR;
}
int32_t freeSlots = std::max<int32_t>(capacity() - size(), 0);
if (item->isStackable()) {
uint32_t n = 0;
if (index == INDEX_WHEREEVER) {
//Iterate through every item and check how much free stackable slots there is.
uint32_t slotIndex = 0;
for (Item* containerItem : itemlist) {
if (containerItem != item && containerItem->equals(item) && containerItem->getItemCount() < 100) {
uint32_t remainder = (100 - containerItem->getItemCount());
if (queryAdd(slotIndex++, *item, remainder, flags) == RETURNVALUE_NOERROR) {
n += remainder;
}
}
}
} else {
const Item* destItem = getItemByIndex(index);
if (item->equals(destItem) && destItem->getItemCount() < 100) {
uint32_t remainder = 100 - destItem->getItemCount();
if (queryAdd(index, *item, remainder, flags) == RETURNVALUE_NOERROR) {
n = remainder;
}
}
}
maxQueryCount = freeSlots * 100 + n;
if (maxQueryCount < count) {
return RETURNVALUE_CONTAINERNOTENOUGHROOM;
}
} else {
maxQueryCount = freeSlots;
if (maxQueryCount == 0) {
return RETURNVALUE_CONTAINERNOTENOUGHROOM;
}
}
return RETURNVALUE_NOERROR;
}
ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const
{
int32_t index = getThingIndex(&thing);
if (index == -1) {
return RETURNVALUE_NOTPOSSIBLE;
}
const Item* item = thing.getItem();
if (item == nullptr) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (count == 0 || (item->isStackable() && count > item->getItemCount())) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) {
return RETURNVALUE_NOTMOVEABLE;
}
return RETURNVALUE_NOERROR;
}
Cylinder* Container::queryDestination(int32_t& index, const Thing &thing, Item** destItem,
uint32_t& flags)
{
if (index == 254 /*move up*/) {
index = INDEX_WHEREEVER;
*destItem = nullptr;
Container* parentContainer = dynamic_cast<Container*>(getParent());
if (parentContainer) {
return parentContainer;
}
return this;
}
if (index == 255 /*add wherever*/) {
index = INDEX_WHEREEVER;
*destItem = nullptr;
} else if (index >= static_cast<int32_t>(capacity())) {
/*
if you have a container, maximize it to show all 20 slots
then you open a bag that is inside the container you will have a bag with 8 slots
and a "grey" area where the other 12 slots where from the container
if you drop the item on that grey area
the client calculates the slot position as if the bag has 20 slots
*/
index = INDEX_WHEREEVER;
*destItem = nullptr;
}
const Item* item = thing.getItem();
if (!item) {
return this;
}
if (g_config.getBoolean(ConfigManager::STACK_CUMULATIVES)) {
bool autoStack = !hasBitSet(FLAG_IGNOREAUTOSTACK, flags);
if (autoStack && item->isStackable() && item->getParent() != this) {
//try find a suitable item to stack with
uint32_t n = 0;
for (Item* listItem : itemlist) {
if (listItem != item && listItem->equals(item) && listItem->getItemCount() < 100) {
*destItem = listItem;
index = n;
return this;
}
++n;
}
}
}
if (index != INDEX_WHEREEVER) {
Item* itemFromIndex = getItemByIndex(index);
if (itemFromIndex) {
*destItem = itemFromIndex;
}
Cylinder* subCylinder = dynamic_cast<Cylinder*>(*destItem);
if (subCylinder) {
index = INDEX_WHEREEVER;
*destItem = nullptr;
return subCylinder;
}
}
return this;
}
void Container::addThing(Thing* thing)
{
return addThing(0, thing);
}
void Container::addThing(int32_t index, Thing* thing)
{
if (index >= static_cast<int32_t>(capacity())) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
Item* item = thing->getItem();
if (item == nullptr) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
item->setParent(this);
itemlist.push_front(item);
updateItemWeight(item->getWeight());
//send change to client
if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) {
onAddContainerItem(item);
}
}
void Container::addItemBack(Item* item)
{
addItem(item);
updateItemWeight(item->getWeight());
//send change to client
if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) {
onAddContainerItem(item);
}
}
void Container::updateThing(Thing* thing, uint16_t itemId, uint32_t count)
{
int32_t index = getThingIndex(thing);
if (index == -1) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
Item* item = thing->getItem();
if (item == nullptr) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
const int32_t oldWeight = item->getWeight();
item->setID(itemId);
item->setSubType(count);
updateItemWeight(-oldWeight + item->getWeight());
//send change to client
if (getParent()) {
onUpdateContainerItem(index, item, item);
}
}
void Container::replaceThing(uint32_t index, Thing* thing)
{
Item* item = thing->getItem();
if (!item) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
Item* replacedItem = getItemByIndex(index);
if (!replacedItem) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
itemlist[index] = item;
item->setParent(this);
updateItemWeight(-static_cast<int32_t>(replacedItem->getWeight()) + item->getWeight());
//send change to client
if (getParent()) {
onUpdateContainerItem(index, replacedItem, item);
}
replacedItem->setParent(nullptr);
}
void Container::removeThing(Thing* thing, uint32_t count)
{
Item* item = thing->getItem();
if (item == nullptr) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
int32_t index = getThingIndex(thing);
if (index == -1) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
if (item->isStackable() && count != item->getItemCount()) {
uint8_t newCount = static_cast<uint8_t>(std::max<int32_t>(0, item->getItemCount() - count));
const int32_t oldWeight = item->getWeight();
item->setItemCount(newCount);
updateItemWeight(-oldWeight + item->getWeight());
//send change to client
if (getParent()) {
onUpdateContainerItem(index, item, item);
}
} else {
updateItemWeight(-static_cast<int32_t>(item->getWeight()));
//send change to client
if (getParent()) {
onRemoveContainerItem(index, item);
}
item->setParent(nullptr);
itemlist.erase(itemlist.begin() + index);
}
}
int32_t Container::getThingIndex(const Thing* thing) const
{
int32_t index = 0;
for (Item* item : itemlist) {
if (item == thing) {
return index;
}
++index;
}
return -1;
}
size_t Container::getFirstIndex() const
{
return 0;
}
size_t Container::getLastIndex() const
{
return size();
}
uint32_t Container::getItemTypeCount(uint16_t itemId, int32_t subType/* = -1*/) const
{
uint32_t count = 0;
for (Item* item : itemlist) {
if (item->getID() == itemId) {
count += countByType(item, subType);
}
}
return count;
}
std::map<uint32_t, uint32_t>& Container::getAllItemTypeCount(std::map<uint32_t, uint32_t>& countMap) const
{
for (Item* item : itemlist) {
countMap[item->getID()] += item->getItemCount();
}
return countMap;
}
Thing* Container::getThing(size_t index) const
{
return getItemByIndex(index);
}
void Container::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t)
{
Cylinder* topParent = getTopParent();
if (topParent->getCreature()) {
topParent->postAddNotification(thing, oldParent, index, LINK_TOPPARENT);
} else if (topParent == this) {
//let the tile class notify surrounding players
if (topParent->getParent()) {
topParent->getParent()->postAddNotification(thing, oldParent, index, LINK_NEAR);
}
} else {
topParent->postAddNotification(thing, oldParent, index, LINK_PARENT);
}
}
void Container::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t)
{
Cylinder* topParent = getTopParent();
if (topParent->getCreature()) {
topParent->postRemoveNotification(thing, newParent, index, LINK_TOPPARENT);
} else if (topParent == this) {
//let the tile class notify surrounding players
if (topParent->getParent()) {
topParent->getParent()->postRemoveNotification(thing, newParent, index, LINK_NEAR);
}
} else {
topParent->postRemoveNotification(thing, newParent, index, LINK_PARENT);
}
}
void Container::internalAddThing(Thing* thing)
{
internalAddThing(0, thing);
}
void Container::internalAddThing(uint32_t, Thing* thing)
{
Item* item = thing->getItem();
if (item == nullptr) {
return;
}
item->setParent(this);
itemlist.push_front(item);
updateItemWeight(item->getWeight());
}
void Container::startDecaying()
{
for (Item* item : itemlist) {
item->startDecaying();
}
}
ContainerIterator Container::iterator() const
{
ContainerIterator cit;
if (!itemlist.empty()) {
cit.over.push_back(this);
cit.cur = itemlist.begin();
}
return cit;
}
Item* ContainerIterator::operator*()
{
return *cur;
}
void ContainerIterator::advance()
{
if (Item* i = *cur) {
if (Container* c = i->getContainer()) {
if (!c->empty()) {
over.push_back(c);
}
}
}
++cur;
if (cur == over.front()->itemlist.end()) {
over.pop_front();
if (!over.empty()) {
cur = over.front()->itemlist.begin();
}
}
}

162
src/container.h Normal file
View File

@@ -0,0 +1,162 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_CONTAINER_H_5590165FD8A2451B98D71F13CD3ED8DC
#define FS_CONTAINER_H_5590165FD8A2451B98D71F13CD3ED8DC
#include <queue>
#include "cylinder.h"
#include "item.h"
class Container;
class DepotLocker;
class ContainerIterator
{
public:
bool hasNext() const {
return !over.empty();
}
void advance();
Item* operator*();
protected:
std::list<const Container*> over;
ItemDeque::const_iterator cur;
friend class Container;
};
class Container : public Item, public Cylinder
{
public:
explicit Container(uint16_t type);
Container(uint16_t type, uint16_t size);
~Container();
// non-copyable
Container(const Container&) = delete;
Container& operator=(const Container&) = delete;
Item* clone() const final;
Container* getContainer() final {
return this;
}
const Container* getContainer() const final {
return this;
}
virtual DepotLocker* getDepotLocker() {
return nullptr;
}
virtual const DepotLocker* getDepotLocker() const {
return nullptr;
}
Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override;
bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) override;
std::string getContentDescription() const;
size_t size() const {
return itemlist.size();
}
bool empty() const {
return itemlist.empty();
}
uint32_t capacity() const {
return maxSize;
}
ContainerIterator iterator() const;
const ItemDeque& getItemList() const {
return itemlist;
}
ItemDeque::const_reverse_iterator getReversedItems() const {
return itemlist.rbegin();
}
ItemDeque::const_reverse_iterator getReversedEnd() const {
return itemlist.rend();
}
bool hasParent() const;
void addItem(Item* item);
Item* getItemByIndex(size_t index) const;
bool isHoldingItem(const Item* item) const;
uint32_t getItemHoldingCount() const;
uint32_t getWeight() const final;
//cylinder implementations
virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count,
uint32_t flags, Creature* actor = nullptr) const override;
ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount,
uint32_t flags) const final;
ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final;
Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem,
uint32_t& flags) final;
void addThing(Thing* thing) final;
void addThing(int32_t index, Thing* thing) final;
void addItemBack(Item* item);
void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final;
void replaceThing(uint32_t index, Thing* thing) final;
void removeThing(Thing* thing, uint32_t count) final;
int32_t getThingIndex(const Thing* thing) const final;
size_t getFirstIndex() const final;
size_t getLastIndex() const final;
uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final;
std::map<uint32_t, uint32_t>& getAllItemTypeCount(std::map<uint32_t, uint32_t>& countMap) const final;
Thing* getThing(size_t index) const final;
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override;
void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override;
void internalAddThing(Thing* thing) final;
void internalAddThing(uint32_t index, Thing* thing) final;
void startDecaying() final;
private:
void onAddContainerItem(Item* item);
void onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem);
void onRemoveContainerItem(uint32_t index, Item* item);
Container* getParentContainer();
void updateItemWeight(int32_t diff);
protected:
std::ostringstream& getContentDescription(std::ostringstream& os) const;
uint32_t maxSize;
uint32_t totalWeight = 0;
ItemDeque itemlist;
uint32_t serializationCount = 0;
friend class ContainerIterator;
friend class IOMapSerialize;
};
#endif

1567
src/creature.cpp Normal file

File diff suppressed because it is too large Load Diff

557
src/creature.h Normal file
View File

@@ -0,0 +1,557 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_CREATURE_H_5363C04015254E298F84E6D59A139508
#define FS_CREATURE_H_5363C04015254E298F84E6D59A139508
#include "map.h"
#include "position.h"
#include "condition.h"
#include "const.h"
#include "tile.h"
#include "enums.h"
#include "creatureevent.h"
typedef std::list<Condition*> ConditionList;
typedef std::list<CreatureEvent*> CreatureEventList;
enum slots_t : uint8_t {
CONST_SLOT_WHEREEVER = 0,
CONST_SLOT_HEAD = 1,
CONST_SLOT_NECKLACE = 2,
CONST_SLOT_BACKPACK = 3,
CONST_SLOT_ARMOR = 4,
CONST_SLOT_RIGHT = 5,
CONST_SLOT_LEFT = 6,
CONST_SLOT_LEGS = 7,
CONST_SLOT_FEET = 8,
CONST_SLOT_RING = 9,
CONST_SLOT_AMMO = 10,
CONST_SLOT_FIRST = CONST_SLOT_HEAD,
CONST_SLOT_LAST = CONST_SLOT_AMMO,
};
struct FindPathParams {
bool fullPathSearch = true;
bool clearSight = true;
bool allowDiagonal = true;
bool keepDistance = false;
int32_t maxSearchDist = 0;
int32_t minTargetDist = -1;
int32_t maxTargetDist = -1;
};
class Map;
class Thing;
class Container;
class Player;
class Monster;
class Npc;
class Item;
class Tile;
class Combat;
static constexpr int32_t EVENT_CREATURECOUNT = 10;
static constexpr int32_t EVENT_CREATURE_THINK_INTERVAL = 1000;
static constexpr int32_t EVENT_CHECK_CREATURE_INTERVAL = (EVENT_CREATURE_THINK_INTERVAL / EVENT_CREATURECOUNT);
class FrozenPathingConditionCall
{
public:
explicit FrozenPathingConditionCall(Position targetPos) : targetPos(std::move(targetPos)) {}
bool operator()(const Position& startPos, const Position& testPos,
const FindPathParams& fpp, int32_t& bestMatchDist) const;
bool isInRange(const Position& startPos, const Position& testPos,
const FindPathParams& fpp) const;
protected:
Position targetPos;
};
//////////////////////////////////////////////////////////////////////
// Defines the Base class for all creatures and base functions which
// every creature has
class Creature : virtual public Thing
{
protected:
Creature();
public:
virtual ~Creature();
// non-copyable
Creature(const Creature&) = delete;
Creature& operator=(const Creature&) = delete;
Creature* getCreature() final {
return this;
}
const Creature* getCreature() const final {
return this;
}
virtual Player* getPlayer() {
return nullptr;
}
virtual const Player* getPlayer() const {
return nullptr;
}
virtual Npc* getNpc() {
return nullptr;
}
virtual const Npc* getNpc() const {
return nullptr;
}
virtual Monster* getMonster() {
return nullptr;
}
virtual const Monster* getMonster() const {
return nullptr;
}
virtual const std::string& getName() const = 0;
virtual const std::string& getNameDescription() const = 0;
virtual void setID() = 0;
void setRemoved() {
isInternalRemoved = true;
}
uint32_t getID() const {
return id;
}
virtual void removeList() = 0;
virtual void addList() = 0;
virtual bool canSee(const Position& pos) const;
virtual bool canSeeCreature(const Creature* creature) const;
virtual RaceType_t getRace() const {
return RACE_NONE;
}
virtual Skulls_t getSkull() const {
return skull;
}
virtual Skulls_t getSkullClient(const Creature* creature) const {
return creature->getSkull();
}
void setSkull(Skulls_t newSkull);
Direction getDirection() const {
return direction;
}
void setDirection(Direction dir) {
direction = dir;
}
int32_t getThrowRange() const final {
return 1;
}
bool isPushable() const override {
return getWalkDelay() <= 0;
}
bool isRemoved() const final {
return isInternalRemoved;
}
virtual bool canSeeInvisibility() const {
return false;
}
virtual bool isInGhostMode() const {
return false;
}
int32_t getWalkDelay(Direction dir) const;
int32_t getWalkDelay() const;
int64_t getTimeSinceLastMove() const;
int64_t getEventStepTicks(bool onlyDelay = false) const;
int64_t getStepDuration(Direction dir) const;
int64_t getStepDuration() const;
virtual int32_t getStepSpeed() const {
return getSpeed();
}
int32_t getSpeed() const {
if (baseSpeed == 0) {
return 0;
}
return (2 * (varSpeed + baseSpeed)) + 80;
}
void setSpeed(int32_t varSpeedDelta) {
int32_t oldSpeed = getSpeed();
varSpeed += varSpeedDelta;
if (getSpeed() <= 0) {
stopEventWalk();
cancelNextWalk = true;
} else if (oldSpeed <= 0 && !listWalkDir.empty()) {
addEventWalk();
}
}
void setBaseSpeed(uint32_t newBaseSpeed) {
baseSpeed = newBaseSpeed;
}
uint32_t getBaseSpeed() const {
return baseSpeed;
}
int32_t getHealth() const {
return health;
}
virtual int32_t getMaxHealth() const {
return healthMax;
}
uint32_t getMana() const {
return mana;
}
virtual uint32_t getMaxMana() const {
return 0;
}
const Outfit_t getCurrentOutfit() const {
return currentOutfit;
}
void setCurrentOutfit(Outfit_t outfit) {
currentOutfit = outfit;
}
const Outfit_t getDefaultOutfit() const {
return defaultOutfit;
}
bool isInvisible() const;
ZoneType_t getZone() const {
return getTile()->getZone();
}
//walk functions
void startAutoWalk(const std::forward_list<Direction>& listDir);
void addEventWalk(bool firstStep = false);
void stopEventWalk();
virtual void goToFollowCreature();
//walk events
virtual void onWalk(Direction& dir);
virtual void onWalkAborted() {}
virtual void onWalkComplete() {}
//follow functions
Creature* getFollowCreature() const {
return followCreature;
}
virtual bool setFollowCreature(Creature* creature);
//follow events
virtual void onFollowCreature(const Creature*) {}
virtual void onFollowCreatureComplete(const Creature*) {}
//combat functions
Creature* getAttackedCreature() {
return attackedCreature;
}
virtual bool setAttackedCreature(Creature* creature);
virtual BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage,
bool checkDefense = false, bool checkArmor = false, bool field = false);
void setMaster(Creature* creature) {
master = creature;
}
bool isSummon() const {
return master != nullptr;
}
Creature* getMaster() const {
return master;
}
void addSummon(Creature* creature);
void removeSummon(Creature* creature);
const std::list<Creature*>& getSummons() const {
return summons;
}
virtual int32_t getArmor() const {
return 0;
}
virtual int32_t getDefense() {
return 0;
}
bool addCondition(Condition* condition, bool force = false);
bool addCombatCondition(Condition* condition);
void removeCondition(ConditionType_t type, ConditionId_t conditionId, bool force = false);
void removeCondition(ConditionType_t type, bool force = false);
void removeCondition(Condition* condition, bool force = false);
void removeCombatCondition(ConditionType_t type);
Condition* getCondition(ConditionType_t type) const;
Condition* getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId = 0) const;
void executeConditions(uint32_t interval);
bool hasCondition(ConditionType_t type, uint32_t subId = 0) const;
virtual bool isImmune(ConditionType_t type) const;
virtual bool isImmune(CombatType_t type) const;
virtual bool isSuppress(ConditionType_t type) const;
virtual uint32_t getDamageImmunities() const {
return 0;
}
virtual uint32_t getConditionImmunities() const {
return 0;
}
virtual uint32_t getConditionSuppressions() const {
return 0;
}
virtual bool isAttackable() const {
return true;
}
virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true);
virtual void changeMana(int32_t manaChange);
void gainHealth(Creature* attacker, int32_t healthGain);
virtual void drainHealth(Creature* attacker, int32_t damage);
virtual void drainMana(Creature* attacker, int32_t manaLoss);
virtual bool challengeCreature(Creature*) {
return false;
}
virtual bool convinceCreature(Creature*) {
return false;
}
void onDeath();
virtual uint64_t getGainedExperience(Creature* attacker) const;
void addDamagePoints(Creature* attacker, int32_t damagePoints);
bool hasBeenAttacked(uint32_t attackerId);
//combat event functions
virtual void onAddCondition(ConditionType_t type);
virtual void onAddCombatCondition(ConditionType_t type);
virtual void onEndCondition(ConditionType_t type);
void onTickCondition(ConditionType_t type, bool& bRemove);
virtual void onCombatRemoveCondition(Condition* condition);
virtual void onAttackedCreature(Creature*) {}
virtual void onAttacked();
virtual void onAttackedCreatureDrainHealth(Creature* target, int32_t points);
virtual void onTargetCreatureGainHealth(Creature*, int32_t) {}
virtual bool onKilledCreature(Creature* target, bool lastHit = true);
virtual void onGainExperience(uint64_t gainExp, Creature* target);
virtual void onAttackedCreatureBlockHit(BlockType_t) {}
virtual void onBlockHit() {}
virtual void onChangeZone(ZoneType_t zone);
virtual void onAttackedCreatureChangeZone(ZoneType_t zone);
virtual void onIdleStatus();
virtual void getCreatureLight(LightInfo& light) const;
virtual void setNormalCreatureLight();
void setCreatureLight(LightInfo light) {
internalLight = light;
}
virtual void onThink(uint32_t interval);
void onAttacking(uint32_t interval);
virtual void onWalk();
virtual bool getNextStep(Direction& dir, uint32_t& flags);
void onAddTileItem(const Tile* tile, const Position& pos);
virtual void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem,
const ItemType& oldType, const Item* newItem, const ItemType& newType);
virtual void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType,
const Item* item);
virtual void onCreatureAppear(Creature* creature, bool isLogin);
virtual void onRemoveCreature(Creature* creature, bool isLogout);
virtual void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos,
const Tile* oldTile, const Position& oldPos, bool teleport);
virtual void onAttackedCreatureDisappear(bool) {}
virtual void onFollowCreatureDisappear(bool) {}
virtual void onCreatureSay(Creature*, SpeakClasses, const std::string&) {}
virtual void onCreatureConvinced(const Creature*, const Creature*) {}
virtual void onPlacedCreature() {}
virtual bool getCombatValues(int32_t&, int32_t&) {
return false;
}
size_t getSummonCount() const {
return summons.size();
}
void setDropLoot(bool lootDrop) {
this->lootDrop = lootDrop;
}
void setLossSkill(bool skillLoss) {
this->skillLoss = skillLoss;
}
//creature script events
bool registerCreatureEvent(const std::string& name);
bool unregisterCreatureEvent(const std::string& name);
Cylinder* getParent() const final {
return tile;
}
void setParent(Cylinder* cylinder) final {
tile = static_cast<Tile*>(cylinder);
position = tile->getPosition();
}
inline const Position& getPosition() const final {
return position;
}
Tile* getTile() final {
return tile;
}
const Tile* getTile() const final {
return tile;
}
int32_t getWalkCache(const Position& pos) const;
const Position& getLastPosition() const {
return lastPosition;
}
void setLastPosition(Position newLastPos) {
lastPosition = newLastPos;
}
static bool canSee(const Position& myPos, const Position& pos, int32_t viewRangeX, int32_t viewRangeY);
double getDamageRatio(Creature* attacker) const;
bool getPathTo(const Position& targetPos, std::forward_list<Direction>& dirList, const FindPathParams& fpp) const;
bool getPathTo(const Position& targetPos, std::forward_list<Direction>& dirList, int32_t minTargetDist, int32_t maxTargetDist, bool fullPathSearch = true, bool clearSight = true, int32_t maxSearchDist = 0) const;
void incrementReferenceCounter() {
++referenceCounter;
}
void decrementReferenceCounter() {
if (--referenceCounter == 0) {
delete this;
}
}
protected:
virtual bool useCacheMap() const {
return false;
}
struct CountBlock_t {
int32_t total;
int64_t ticks;
};
static constexpr int32_t mapWalkWidth = Map::maxViewportX * 2 + 1;
static constexpr int32_t mapWalkHeight = Map::maxViewportY * 2 + 1;
static constexpr int32_t maxWalkCacheWidth = (mapWalkWidth - 1) / 2;
static constexpr int32_t maxWalkCacheHeight = (mapWalkHeight - 1) / 2;
Position position;
typedef std::map<uint32_t, CountBlock_t> CountMap;
CountMap damageMap;
std::list<Creature*> summons;
CreatureEventList eventsList;
ConditionList conditions;
std::forward_list<Direction> listWalkDir;
Tile* tile = nullptr;
Creature* attackedCreature = nullptr;
Creature* master = nullptr;
Creature* followCreature = nullptr;
int64_t earliestDefendTime = 0;
int64_t lastDefendTime = 0;
uint64_t lastWalkUpdate = 0;
uint64_t lastStep = 0;
uint32_t referenceCounter = 0;
uint32_t id = 0;
uint32_t scriptEventsBitField = 0;
uint32_t eventWalk = 0;
uint32_t walkUpdateTicks = 0;
uint32_t lastHitCreatureId = 0;
uint32_t blockCount = 0;
uint32_t blockTicks = 0;
uint32_t lastStepCost = 1;
uint32_t baseSpeed = 70;
uint32_t mana = 0;
uint32_t latestKillEvent = 0;
int32_t varSpeed = 0;
int32_t health = 1000;
int32_t healthMax = 1000;
Outfit_t currentOutfit;
Outfit_t defaultOutfit;
Position lastPosition;
LightInfo internalLight;
Direction direction = DIRECTION_SOUTH;
Skulls_t skull = SKULL_NONE;
bool localMapCache[mapWalkHeight][mapWalkWidth] = {{ false }};
bool isInternalRemoved = false;
bool isMapLoaded = false;
bool isUpdatingPath = false;
bool creatureCheck = false;
bool inCheckCreaturesVector = false;
bool skillLoss = true;
bool lootDrop = true;
bool cancelNextWalk = false;
bool hasFollowPath = false;
bool forceUpdateFollowPath = false;
//creature script events
bool hasEventRegistered(CreatureEventType_t event) const {
return (0 != (scriptEventsBitField & (static_cast<uint32_t>(1) << event)));
}
CreatureEventList getCreatureEvents(CreatureEventType_t type);
void updateMapCache();
void updateTileCache(const Tile* tile, int32_t dx, int32_t dy);
void updateTileCache(const Tile* tile, const Position& pos);
void onCreatureDisappear(const Creature* creature, bool isLogout);
virtual void doAttacking(uint32_t) {}
virtual bool hasExtraSwing() {
return false;
}
virtual uint64_t getLostExperience() const {
return 0;
}
virtual void dropLoot(Container*, Creature*) {}
virtual uint16_t getLookCorpse() const {
return 0;
}
virtual void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const;
virtual void death(Creature*) {}
virtual bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified);
virtual Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature);
friend class Game;
friend class Map;
friend class LuaScriptInterface;
friend class Combat;
};
#endif

434
src/creatureevent.cpp Normal file
View File

@@ -0,0 +1,434 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "creatureevent.h"
#include "tools.h"
#include "player.h"
CreatureEvents::CreatureEvents() :
scriptInterface("CreatureScript Interface")
{
scriptInterface.initState();
}
CreatureEvents::~CreatureEvents()
{
for (const auto& it : creatureEvents) {
delete it.second;
}
}
void CreatureEvents::clear()
{
//clear creature events
for (const auto& it : creatureEvents) {
it.second->clearEvent();
}
//clear lua state
scriptInterface.reInitState();
}
LuaScriptInterface& CreatureEvents::getScriptInterface()
{
return scriptInterface;
}
std::string CreatureEvents::getScriptBaseName() const
{
return "creaturescripts";
}
Event* CreatureEvents::getEvent(const std::string& nodeName)
{
if (strcasecmp(nodeName.c_str(), "event") != 0) {
return nullptr;
}
return new CreatureEvent(&scriptInterface);
}
bool CreatureEvents::registerEvent(Event* event, const pugi::xml_node&)
{
CreatureEvent* creatureEvent = static_cast<CreatureEvent*>(event); //event is guaranteed to be a CreatureEvent
if (creatureEvent->getEventType() == CREATURE_EVENT_NONE) {
std::cout << "Error: [CreatureEvents::registerEvent] Trying to register event without type!" << std::endl;
return false;
}
CreatureEvent* oldEvent = getEventByName(creatureEvent->getName(), false);
if (oldEvent) {
//if there was an event with the same that is not loaded
//(happens when realoading), it is reused
if (!oldEvent->isLoaded() && oldEvent->getEventType() == creatureEvent->getEventType()) {
oldEvent->copyEvent(creatureEvent);
}
return false;
} else {
//if not, register it normally
creatureEvents[creatureEvent->getName()] = creatureEvent;
return true;
}
}
CreatureEvent* CreatureEvents::getEventByName(const std::string& name, bool forceLoaded /*= true*/)
{
auto it = creatureEvents.find(name);
if (it != creatureEvents.end()) {
if (!forceLoaded || it->second->isLoaded()) {
return it->second;
}
}
return nullptr;
}
bool CreatureEvents::playerLogin(Player* player) const
{
//fire global event if is registered
for (const auto& it : creatureEvents) {
if (it.second->getEventType() == CREATURE_EVENT_LOGIN) {
if (!it.second->executeOnLogin(player)) {
return false;
}
}
}
return true;
}
bool CreatureEvents::playerLogout(Player* player) const
{
//fire global event if is registered
for (const auto& it : creatureEvents) {
if (it.second->getEventType() == CREATURE_EVENT_LOGOUT) {
if (!it.second->executeOnLogout(player)) {
return false;
}
}
}
return true;
}
bool CreatureEvents::playerAdvance(Player* player, skills_t skill, uint32_t oldLevel,
uint32_t newLevel)
{
for (const auto& it : creatureEvents) {
if (it.second->getEventType() == CREATURE_EVENT_ADVANCE) {
if (!it.second->executeAdvance(player, skill, oldLevel, newLevel)) {
return false;
}
}
}
return true;
}
/////////////////////////////////////
CreatureEvent::CreatureEvent(LuaScriptInterface* interface) :
Event(interface), type(CREATURE_EVENT_NONE), loaded(false) {}
bool CreatureEvent::configureEvent(const pugi::xml_node& node)
{
// Name that will be used in monster xml files and
// lua function to register events to reference this event
pugi::xml_attribute nameAttribute = node.attribute("name");
if (!nameAttribute) {
std::cout << "[Error - CreatureEvent::configureEvent] Missing name for creature event" << std::endl;
return false;
}
eventName = nameAttribute.as_string();
pugi::xml_attribute typeAttribute = node.attribute("type");
if (!typeAttribute) {
std::cout << "[Error - CreatureEvent::configureEvent] Missing type for creature event: " << eventName << std::endl;
return false;
}
std::string tmpStr = asLowerCaseString(typeAttribute.as_string());
if (tmpStr == "login") {
type = CREATURE_EVENT_LOGIN;
} else if (tmpStr == "logout") {
type = CREATURE_EVENT_LOGOUT;
} else if (tmpStr == "think") {
type = CREATURE_EVENT_THINK;
} else if (tmpStr == "preparedeath") {
type = CREATURE_EVENT_PREPAREDEATH;
} else if (tmpStr == "death") {
type = CREATURE_EVENT_DEATH;
} else if (tmpStr == "kill") {
type = CREATURE_EVENT_KILL;
} else if (tmpStr == "advance") {
type = CREATURE_EVENT_ADVANCE;
} else if (tmpStr == "extendedopcode") {
type = CREATURE_EVENT_EXTENDED_OPCODE;
} else {
std::cout << "[Error - CreatureEvent::configureEvent] Invalid type for creature event: " << eventName << std::endl;
return false;
}
loaded = true;
return true;
}
std::string CreatureEvent::getScriptEventName() const
{
//Depending on the type script event name is different
switch (type) {
case CREATURE_EVENT_LOGIN:
return "onLogin";
case CREATURE_EVENT_LOGOUT:
return "onLogout";
case CREATURE_EVENT_THINK:
return "onThink";
case CREATURE_EVENT_PREPAREDEATH:
return "onPrepareDeath";
case CREATURE_EVENT_DEATH:
return "onDeath";
case CREATURE_EVENT_KILL:
return "onKill";
case CREATURE_EVENT_ADVANCE:
return "onAdvance";
case CREATURE_EVENT_EXTENDED_OPCODE:
return "onExtendedOpcode";
case CREATURE_EVENT_NONE:
default:
return std::string();
}
}
void CreatureEvent::copyEvent(CreatureEvent* creatureEvent)
{
scriptId = creatureEvent->scriptId;
scriptInterface = creatureEvent->scriptInterface;
scripted = creatureEvent->scripted;
loaded = creatureEvent->loaded;
}
void CreatureEvent::clearEvent()
{
scriptId = 0;
scriptInterface = nullptr;
scripted = false;
loaded = false;
}
bool CreatureEvent::executeOnLogin(Player* player)
{
//onLogin(player)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - CreatureEvent::executeOnLogin] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata(L, player);
LuaScriptInterface::setMetatable(L, -1, "Player");
return scriptInterface->callFunction(1);
}
bool CreatureEvent::executeOnLogout(Player* player)
{
//onLogout(player)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - CreatureEvent::executeOnLogout] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata(L, player);
LuaScriptInterface::setMetatable(L, -1, "Player");
return scriptInterface->callFunction(1);
}
bool CreatureEvent::executeOnThink(Creature* creature, uint32_t interval)
{
//onThink(creature, interval)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - CreatureEvent::executeOnThink] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata<Creature>(L, creature);
LuaScriptInterface::setCreatureMetatable(L, -1, creature);
lua_pushnumber(L, interval);
return scriptInterface->callFunction(2);
}
bool CreatureEvent::executeOnPrepareDeath(Creature* creature, Creature* killer)
{
//onPrepareDeath(creature, killer)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - CreatureEvent::executeOnPrepareDeath] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata<Creature>(L, creature);
LuaScriptInterface::setCreatureMetatable(L, -1, creature);
if (killer) {
LuaScriptInterface::pushUserdata<Creature>(L, killer);
LuaScriptInterface::setCreatureMetatable(L, -1, killer);
} else {
lua_pushnil(L);
}
return scriptInterface->callFunction(2);
}
bool CreatureEvent::executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified)
{
//onDeath(creature, corpse, lasthitkiller, mostdamagekiller, lasthitunjustified, mostdamageunjustified)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - CreatureEvent::executeOnDeath] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata<Creature>(L, creature);
LuaScriptInterface::setCreatureMetatable(L, -1, creature);
LuaScriptInterface::pushThing(L, corpse);
if (killer) {
LuaScriptInterface::pushUserdata<Creature>(L, killer);
LuaScriptInterface::setCreatureMetatable(L, -1, killer);
} else {
lua_pushnil(L);
}
if (mostDamageKiller) {
LuaScriptInterface::pushUserdata<Creature>(L, mostDamageKiller);
LuaScriptInterface::setCreatureMetatable(L, -1, mostDamageKiller);
} else {
lua_pushnil(L);
}
LuaScriptInterface::pushBoolean(L, lastHitUnjustified);
LuaScriptInterface::pushBoolean(L, mostDamageUnjustified);
return scriptInterface->callFunction(6);
}
bool CreatureEvent::executeAdvance(Player* player, skills_t skill, uint32_t oldLevel,
uint32_t newLevel)
{
//onAdvance(player, skill, oldLevel, newLevel)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - CreatureEvent::executeAdvance] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata(L, player);
LuaScriptInterface::setMetatable(L, -1, "Player");
lua_pushnumber(L, static_cast<uint32_t>(skill));
lua_pushnumber(L, oldLevel);
lua_pushnumber(L, newLevel);
return scriptInterface->callFunction(4);
}
void CreatureEvent::executeOnKill(Creature* creature, Creature* target)
{
//onKill(creature, target)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - CreatureEvent::executeOnKill] Call stack overflow" << std::endl;
return;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata<Creature>(L, creature);
LuaScriptInterface::setCreatureMetatable(L, -1, creature);
LuaScriptInterface::pushUserdata<Creature>(L, target);
LuaScriptInterface::setCreatureMetatable(L, -1, target);
scriptInterface->callVoidFunction(2);
}
void CreatureEvent::executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer)
{
//onExtendedOpcode(player, opcode, buffer)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - CreatureEvent::executeExtendedOpcode] Call stack overflow" << std::endl;
return;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata<Player>(L, player);
LuaScriptInterface::setMetatable(L, -1, "Player");
lua_pushnumber(L, opcode);
LuaScriptInterface::pushString(L, buffer);
scriptInterface->callVoidFunction(3);
}

111
src/creatureevent.h Normal file
View File

@@ -0,0 +1,111 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_CREATUREEVENT_H_73FCAF4608CB41399D53C919316646A9
#define FS_CREATUREEVENT_H_73FCAF4608CB41399D53C919316646A9
#include "luascript.h"
#include "baseevents.h"
#include "enums.h"
enum CreatureEventType_t {
CREATURE_EVENT_NONE,
CREATURE_EVENT_LOGIN,
CREATURE_EVENT_LOGOUT,
CREATURE_EVENT_THINK,
CREATURE_EVENT_PREPAREDEATH,
CREATURE_EVENT_DEATH,
CREATURE_EVENT_KILL,
CREATURE_EVENT_ADVANCE,
CREATURE_EVENT_EXTENDED_OPCODE, // otclient additional network opcodes
};
class CreatureEvent;
class CreatureEvents final : public BaseEvents
{
public:
CreatureEvents();
~CreatureEvents();
// non-copyable
CreatureEvents(const CreatureEvents&) = delete;
CreatureEvents& operator=(const CreatureEvents&) = delete;
// global events
bool playerLogin(Player* player) const;
bool playerLogout(Player* player) const;
bool playerAdvance(Player* player, skills_t, uint32_t, uint32_t);
CreatureEvent* getEventByName(const std::string& name, bool forceLoaded = true);
protected:
LuaScriptInterface& getScriptInterface() final;
std::string getScriptBaseName() const final;
Event* getEvent(const std::string& nodeName) final;
bool registerEvent(Event* event, const pugi::xml_node& node) final;
void clear() final;
//creature events
typedef std::map<std::string, CreatureEvent*> CreatureEventList;
CreatureEventList creatureEvents;
LuaScriptInterface scriptInterface;
};
class CreatureEvent final : public Event
{
public:
explicit CreatureEvent(LuaScriptInterface* interface);
bool configureEvent(const pugi::xml_node& node) final;
CreatureEventType_t getEventType() const {
return type;
}
const std::string& getName() const {
return eventName;
}
bool isLoaded() const {
return loaded;
}
void clearEvent();
void copyEvent(CreatureEvent* creatureEvent);
//scripting
bool executeOnLogin(Player* player);
bool executeOnLogout(Player* player);
bool executeOnThink(Creature* creature, uint32_t interval);
bool executeOnPrepareDeath(Creature* creature, Creature* killer);
bool executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified);
void executeOnKill(Creature* creature, Creature* target);
bool executeAdvance(Player* player, skills_t, uint32_t, uint32_t);
void executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer);
//
protected:
std::string getScriptEventName() const final;
std::string eventName;
CreatureEventType_t type;
bool loaded;
};
#endif

69
src/cylinder.cpp Normal file
View File

@@ -0,0 +1,69 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "cylinder.h"
VirtualCylinder* VirtualCylinder::virtualCylinder = new VirtualCylinder;
int32_t Cylinder::getThingIndex(const Thing*) const
{
return -1;
}
size_t Cylinder::getFirstIndex() const
{
return 0;
}
size_t Cylinder::getLastIndex() const
{
return 0;
}
uint32_t Cylinder::getItemTypeCount(uint16_t, int32_t) const
{
return 0;
}
std::map<uint32_t, uint32_t>& Cylinder::getAllItemTypeCount(std::map<uint32_t, uint32_t>& countMap) const
{
return countMap;
}
Thing* Cylinder::getThing(size_t) const
{
return nullptr;
}
void Cylinder::internalAddThing(Thing*)
{
//
}
void Cylinder::internalAddThing(uint32_t, Thing*)
{
//
}
void Cylinder::startDecaying()
{
//
}

250
src/cylinder.h Normal file
View File

@@ -0,0 +1,250 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_CYLINDER_H_54BBCEB2A5B7415DAD837E4D58115150
#define FS_CYLINDER_H_54BBCEB2A5B7415DAD837E4D58115150
#include "enums.h"
#include "thing.h"
class Item;
class Creature;
static constexpr int32_t INDEX_WHEREEVER = -1;
enum cylinderflags_t {
FLAG_NOLIMIT = 1 << 0, //Bypass limits like capacity/container limits, blocking items/creatures etc.
FLAG_IGNOREBLOCKITEM = 1 << 1, //Bypass movable blocking item checks
FLAG_IGNOREBLOCKCREATURE = 1 << 2, //Bypass creature checks
FLAG_CHILDISOWNER = 1 << 3, //Used by containers to query capacity of the carrier (player)
FLAG_PATHFINDING = 1 << 4, //An additional check is done for floor changing/teleport items
FLAG_IGNOREFIELDDAMAGE = 1 << 5, //Bypass field damage checks
FLAG_IGNORENOTMOVEABLE = 1 << 6, //Bypass check for mobility
FLAG_IGNOREAUTOSTACK = 1 << 7, //queryDestination will not try to stack items together
FLAG_PLACECHECK = 1 << 8, //Special check for placing the monster
};
enum cylinderlink_t {
LINK_OWNER,
LINK_PARENT,
LINK_TOPPARENT,
LINK_NEAR,
};
class Cylinder : virtual public Thing
{
public:
/**
* Query if the cylinder can add an object
* \param index points to the destination index (inventory slot/container position)
* -1 is a internal value and means add to a empty position, with no destItem
* \param thing the object to move/add
* \param count is the amount that we want to move/add
* \param flags if FLAG_CHILDISOWNER if set the query is from a child-cylinder (check cap etc.)
* if FLAG_NOLIMIT is set blocking items/container limits is ignored
* \param actor the creature trying to add the thing
* \returns ReturnValue holds the return value
*/
virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count,
uint32_t flags, Creature* actor = nullptr) const = 0;
/**
* Query the cylinder how much it can accept
* \param index points to the destination index (inventory slot/container position)
* -1 is a internal value and means add to a empty position, with no destItem
* \param thing the object to move/add
* \param count is the amount that we want to move/add
* \param maxQueryCount is the max amount that the cylinder can accept
* \param flags optional flags to modify the default behaviour
* \returns ReturnValue holds the return value
*/
virtual ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount,
uint32_t flags) const = 0;
/**
* Query if the cylinder can remove an object
* \param thing the object to move/remove
* \param count is the amount that we want to remove
* \param flags optional flags to modify the default behaviour
* \returns ReturnValue holds the return value
*/
virtual ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const = 0;
/**
* Query the destination cylinder
* \param index points to the destination index (inventory slot/container position),
* -1 is a internal value and means add to a empty position, with no destItem
* this method can change the index to point to the new cylinder index
* \param destItem is the destination object
* \param flags optional flags to modify the default behaviour
* this method can modify the flags
* \returns Cylinder returns the destination cylinder
*/
virtual Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem,
uint32_t& flags) = 0;
/**
* Add the object to the cylinder
* \param thing is the object to add
*/
virtual void addThing(Thing* thing) = 0;
/**
* Add the object to the cylinder
* \param index points to the destination index (inventory slot/container position)
* \param thing is the object to add
*/
virtual void addThing(int32_t index, Thing* thing) = 0;
/**
* Update the item count or type for an object
* \param thing is the object to update
* \param itemId is the new item id
* \param count is the new count value
*/
virtual void updateThing(Thing* thing, uint16_t itemId, uint32_t count) = 0;
/**
* Replace an object with a new
* \param index is the position to change (inventory slot/container position)
* \param thing is the object to update
*/
virtual void replaceThing(uint32_t index, Thing* thing) = 0;
/**
* Remove an object
* \param thing is the object to delete
* \param count is the new count value
*/
virtual void removeThing(Thing* thing, uint32_t count) = 0;
/**
* Is sent after an operation (move/add) to update internal values
* \param thing is the object that has been added
* \param index is the objects new index value
* \param link holds the relation the object has to the cylinder
*/
virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) = 0;
/**
* Is sent after an operation (move/remove) to update internal values
* \param thing is the object that has been removed
* \param index is the previous index of the removed object
* \param link holds the relation the object has to the cylinder
*/
virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) = 0;
/**
* Gets the index of an object
* \param thing the object to get the index value from
* \returns the index of the object, returns -1 if not found
*/
virtual int32_t getThingIndex(const Thing* thing) const;
/**
* Returns the first index
* \returns the first index, if not implemented 0 is returned
*/
virtual size_t getFirstIndex() const;
/**
* Returns the last index
* \returns the last index, if not implemented 0 is returned
*/
virtual size_t getLastIndex() const;
/**
* Gets the object based on index
* \returns the object, returns nullptr if not found
*/
virtual Thing* getThing(size_t index) const;
/**
* Get the amount of items of a certain type
* \param itemId is the item type to the get the count of
* \param subType is the extra type an item can have such as charges/fluidtype, -1 means not used
* \returns the amount of items of the asked item type
*/
virtual uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const;
/**
* Get the amount of items of a all types
* \param countMap a map to put the itemID:count mapping in
* \returns a map mapping item id to count (same as first argument)
*/
virtual std::map<uint32_t, uint32_t>& getAllItemTypeCount(std::map<uint32_t, uint32_t>& countMap) const;
/**
* Adds an object to the cylinder without sending to the client(s)
* \param thing is the object to add
*/
virtual void internalAddThing(Thing* thing);
/**
* Adds an object to the cylinder without sending to the client(s)
* \param thing is the object to add
* \param index points to the destination index (inventory slot/container position)
*/
virtual void internalAddThing(uint32_t index, Thing* thing);
virtual void startDecaying();
};
class VirtualCylinder final : public Cylinder
{
public:
static VirtualCylinder* virtualCylinder;
virtual ReturnValue queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature* = nullptr) const override {
return RETURNVALUE_NOTPOSSIBLE;
}
virtual ReturnValue queryMaxCount(int32_t, const Thing&, uint32_t, uint32_t&, uint32_t) const override {
return RETURNVALUE_NOTPOSSIBLE;
}
virtual ReturnValue queryRemove(const Thing&, uint32_t, uint32_t) const override {
return RETURNVALUE_NOTPOSSIBLE;
}
virtual Cylinder* queryDestination(int32_t&, const Thing&, Item**, uint32_t&) override {
return nullptr;
}
virtual void addThing(Thing*) override {}
virtual void addThing(int32_t, Thing*) override {}
virtual void updateThing(Thing*, uint16_t, uint32_t) override {}
virtual void replaceThing(uint32_t, Thing*) override {}
virtual void removeThing(Thing*, uint32_t) override {}
virtual void postAddNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {}
virtual void postRemoveNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {}
bool isPushable() const override {
return false;
}
int32_t getThrowRange() const override {
return 1;
}
std::string getDescription(int32_t) const override {
return {};
}
bool isRemoved() const override {
return false;
}
};
#endif

295
src/database.cpp Normal file
View File

@@ -0,0 +1,295 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "configmanager.h"
#include "database.h"
#include <mysql/errmsg.h>
extern ConfigManager g_config;
Database::~Database()
{
if (handle != nullptr) {
mysql_close(handle);
}
}
bool Database::connect()
{
// connection handle initialization
handle = mysql_init(nullptr);
if (!handle) {
std::cout << std::endl << "Failed to initialize MySQL connection handle." << std::endl;
return false;
}
// automatic reconnect
bool reconnect = true;
mysql_options(handle, MYSQL_OPT_RECONNECT, &reconnect);
// connects to database
if (!mysql_real_connect(handle, g_config.getString(ConfigManager::MYSQL_HOST).c_str(), g_config.getString(ConfigManager::MYSQL_USER).c_str(), g_config.getString(ConfigManager::MYSQL_PASS).c_str(), g_config.getString(ConfigManager::MYSQL_DB).c_str(), g_config.getNumber(ConfigManager::SQL_PORT), g_config.getString(ConfigManager::MYSQL_SOCK).c_str(), 0)) {
std::cout << std::endl << "MySQL Error Message: " << mysql_error(handle) << std::endl;
return false;
}
DBResult_ptr result = storeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'");
if (result) {
maxPacketSize = result->getNumber<uint64_t>("Value");
}
return true;
}
bool Database::beginTransaction()
{
if (!executeQuery("BEGIN")) {
return false;
}
databaseLock.lock();
return true;
}
bool Database::rollback()
{
if (mysql_rollback(handle) != 0) {
std::cout << "[Error - mysql_rollback] Message: " << mysql_error(handle) << std::endl;
databaseLock.unlock();
return false;
}
databaseLock.unlock();
return true;
}
bool Database::commit()
{
if (mysql_commit(handle) != 0) {
std::cout << "[Error - mysql_commit] Message: " << mysql_error(handle) << std::endl;
databaseLock.unlock();
return false;
}
databaseLock.unlock();
return true;
}
bool Database::executeQuery(const std::string& query)
{
bool success = true;
// executes the query
databaseLock.lock();
while (mysql_real_query(handle, query.c_str(), query.length()) != 0) {
std::cout << "[Error - mysql_real_query] Query: " << query.substr(0, 256) << std::endl << "Message: " << mysql_error(handle) << std::endl;
auto error = mysql_errno(handle);
if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) {
success = false;
break;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
MYSQL_RES* m_res = mysql_store_result(handle);
databaseLock.unlock();
if (m_res) {
mysql_free_result(m_res);
}
return success;
}
DBResult_ptr Database::storeQuery(const std::string& query)
{
databaseLock.lock();
retry:
while (mysql_real_query(handle, query.c_str(), query.length()) != 0) {
std::cout << "[Error - mysql_real_query] Query: " << query << std::endl << "Message: " << mysql_error(handle) << std::endl;
auto error = mysql_errno(handle);
if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) {
break;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// we should call that every time as someone would call executeQuery('SELECT...')
// as it is described in MySQL manual: "it doesn't hurt" :P
MYSQL_RES* res = mysql_store_result(handle);
if (res == nullptr) {
std::cout << "[Error - mysql_store_result] Query: " << query << std::endl << "Message: " << mysql_error(handle) << std::endl;
auto error = mysql_errno(handle);
if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) {
databaseLock.unlock();
return nullptr;
}
goto retry;
}
databaseLock.unlock();
// retrieving results of query
DBResult_ptr result = std::make_shared<DBResult>(res);
if (!result->hasNext()) {
return nullptr;
}
return result;
}
std::string Database::escapeString(const std::string& s) const
{
return escapeBlob(s.c_str(), s.length());
}
std::string Database::escapeBlob(const char* s, uint32_t length) const
{
// the worst case is 2n + 1
size_t maxLength = (length * 2) + 1;
std::string escaped;
escaped.reserve(maxLength + 2);
escaped.push_back('\'');
if (length != 0) {
char* output = new char[maxLength];
mysql_real_escape_string(handle, output, s, length);
escaped.append(output);
delete[] output;
}
escaped.push_back('\'');
return escaped;
}
DBResult::DBResult(MYSQL_RES* res)
{
handle = res;
size_t i = 0;
MYSQL_FIELD* field = mysql_fetch_field(handle);
while (field) {
listNames[field->name] = i++;
field = mysql_fetch_field(handle);
}
row = mysql_fetch_row(handle);
}
DBResult::~DBResult()
{
mysql_free_result(handle);
}
std::string DBResult::getString(const std::string& s) const
{
auto it = listNames.find(s);
if (it == listNames.end()) {
std::cout << "[Error - DBResult::getString] Column '" << s << "' does not exist in result set." << std::endl;
return std::string();
}
if (row[it->second] == nullptr) {
return std::string();
}
return std::string(row[it->second]);
}
const char* DBResult::getStream(const std::string& s, unsigned long& size) const
{
auto it = listNames.find(s);
if (it == listNames.end()) {
std::cout << "[Error - DBResult::getStream] Column '" << s << "' doesn't exist in the result set" << std::endl;
size = 0;
return nullptr;
}
if (row[it->second] == nullptr) {
size = 0;
return nullptr;
}
size = mysql_fetch_lengths(handle)[it->second];
return row[it->second];
}
bool DBResult::hasNext() const
{
return row != nullptr;
}
bool DBResult::next()
{
row = mysql_fetch_row(handle);
return row != nullptr;
}
DBInsert::DBInsert(std::string query) : query(std::move(query))
{
this->length = this->query.length();
}
bool DBInsert::addRow(const std::string& row)
{
// adds new row to buffer
const size_t rowLength = row.length();
length += rowLength;
if (length > Database::getInstance()->getMaxPacketSize() && !execute()) {
return false;
}
if (values.empty()) {
values.reserve(rowLength + 2);
values.push_back('(');
values.append(row);
values.push_back(')');
} else {
values.reserve(values.length() + rowLength + 3);
values.push_back(',');
values.push_back('(');
values.append(row);
values.push_back(')');
}
return true;
}
bool DBInsert::addRow(std::ostringstream& row)
{
bool ret = addRow(row.str());
row.str(std::string());
return ret;
}
bool DBInsert::execute()
{
if (values.empty()) {
return true;
}
// executes buffer
bool res = Database::getInstance()->executeQuery(query + values);
values.clear();
length = query.length();
return res;
}

243
src/database.h Normal file
View File

@@ -0,0 +1,243 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693
#define FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693
#include <boost/lexical_cast.hpp>
#include <mysql/mysql.h>
class DBResult;
typedef std::shared_ptr<DBResult> DBResult_ptr;
class Database
{
public:
Database() = default;
~Database();
// non-copyable
Database(const Database&) = delete;
Database& operator=(const Database&) = delete;
/**
* Singleton implementation.
*
* @return database connection handler singleton
*/
static Database* getInstance()
{
static Database instance;
return &instance;
}
/**
* Connects to the database
*
* @return true on successful connection, false on error
*/
bool connect();
/**
* Executes command.
*
* Executes query which doesn't generates results (eg. INSERT, UPDATE, DELETE...).
*
* @param query command
* @return true on success, false on error
*/
bool executeQuery(const std::string& query);
/**
* Queries database.
*
* Executes query which generates results (mostly SELECT).
*
* @return results object (nullptr on error)
*/
DBResult_ptr storeQuery(const std::string& query);
/**
* Escapes string for query.
*
* Prepares string to fit SQL queries including quoting it.
*
* @param s string to be escaped
* @return quoted string
*/
std::string escapeString(const std::string& s) const;
/**
* Escapes binary stream for query.
*
* Prepares binary stream to fit SQL queries.
*
* @param s binary stream
* @param length stream length
* @return quoted string
*/
std::string escapeBlob(const char* s, uint32_t length) const;
/**
* Retrieve id of last inserted row
*
* @return id on success, 0 if last query did not result on any rows with auto_increment keys
*/
uint64_t getLastInsertId() const {
return static_cast<uint64_t>(mysql_insert_id(handle));
}
/**
* Get database engine version
*
* @return the database engine version
*/
static const char* getClientVersion() {
return mysql_get_client_info();
}
uint64_t getMaxPacketSize() const {
return maxPacketSize;
}
protected:
/**
* Transaction related methods.
*
* Methods for starting, commiting and rolling back transaction. Each of the returns boolean value.
*
* @return true on success, false on error
*/
bool beginTransaction();
bool rollback();
bool commit();
private:
MYSQL* handle = nullptr;
std::recursive_mutex databaseLock;
uint64_t maxPacketSize = 1048576;
friend class DBTransaction;
};
class DBResult
{
public:
explicit DBResult(MYSQL_RES* res);
~DBResult();
// non-copyable
DBResult(const DBResult&) = delete;
DBResult& operator=(const DBResult&) = delete;
template<typename T>
T getNumber(const std::string& s) const
{
auto it = listNames.find(s);
if (it == listNames.end()) {
std::cout << "[Error - DBResult::getNumber] Column '" << s << "' doesn't exist in the result set" << std::endl;
return static_cast<T>(0);
}
if (row[it->second] == nullptr) {
return static_cast<T>(0);
}
T data;
try {
data = boost::lexical_cast<T>(row[it->second]);
} catch (boost::bad_lexical_cast&) {
data = 0;
}
return data;
}
std::string getString(const std::string& s) const;
const char* getStream(const std::string& s, unsigned long& size) const;
bool hasNext() const;
bool next();
private:
MYSQL_RES* handle;
MYSQL_ROW row;
std::map<std::string, size_t> listNames;
friend class Database;
};
/**
* INSERT statement.
*/
class DBInsert
{
public:
explicit DBInsert(std::string query);
bool addRow(const std::string& row);
bool addRow(std::ostringstream& row);
bool execute();
protected:
std::string query;
std::string values;
size_t length;
};
class DBTransaction
{
public:
constexpr DBTransaction() = default;
~DBTransaction() {
if (state == STATE_START) {
Database::getInstance()->rollback();
}
}
// non-copyable
DBTransaction(const DBTransaction&) = delete;
DBTransaction& operator=(const DBTransaction&) = delete;
bool begin() {
state = STATE_START;
return Database::getInstance()->beginTransaction();
}
bool commit() {
if (state != STATE_START) {
return false;
}
state = STEATE_COMMIT;
return Database::getInstance()->commit();
}
private:
enum TransactionStates_t {
STATE_NO_START,
STATE_START,
STEATE_COMMIT,
};
TransactionStates_t state = STATE_NO_START;
};
#endif

117
src/databasemanager.cpp Normal file
View File

@@ -0,0 +1,117 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "configmanager.h"
#include "databasemanager.h"
#include "luascript.h"
extern ConfigManager g_config;
bool DatabaseManager::optimizeTables()
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `TABLE_NAME` FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `DATA_FREE` > 0";
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return false;
}
do {
std::string tableName = result->getString("TABLE_NAME");
std::cout << "> Optimizing table " << tableName << "..." << std::flush;
query.str(std::string());
query << "OPTIMIZE TABLE `" << tableName << '`';
if (db->executeQuery(query.str())) {
std::cout << " [success]" << std::endl;
} else {
std::cout << " [failed]" << std::endl;
}
} while (result->next());
return true;
}
bool DatabaseManager::tableExists(const std::string& tableName)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `TABLE_NAME` = " << db->escapeString(tableName) << " LIMIT 1";
return db->storeQuery(query.str()).get() != nullptr;
}
bool DatabaseManager::isDatabaseSetup()
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB));
return db->storeQuery(query.str()).get() != nullptr;
}
int32_t DatabaseManager::getDatabaseVersion()
{
if (!tableExists("server_config")) {
Database* db = Database::getInstance();
db->executeQuery("CREATE TABLE `server_config` (`config` VARCHAR(50) NOT NULL, `value` VARCHAR(256) NOT NULL DEFAULT '', UNIQUE(`config`)) ENGINE = InnoDB");
db->executeQuery("INSERT INTO `server_config` VALUES ('db_version', 0)");
return 0;
}
int32_t version = 0;
if (getDatabaseConfig("db_version", version)) {
return version;
}
return -1;
}
bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& value)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `value` FROM `server_config` WHERE `config` = " << db->escapeString(config);
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return false;
}
value = result->getNumber<int32_t>("value");
return true;
}
void DatabaseManager::registerDatabaseConfig(const std::string& config, int32_t value)
{
Database* db = Database::getInstance();
std::ostringstream query;
int32_t tmp;
if (!getDatabaseConfig(config, tmp)) {
query << "INSERT INTO `server_config` VALUES (" << db->escapeString(config) << ", '" << value << "')";
} else {
query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db->escapeString(config);
}
db->executeQuery(query.str());
}

37
src/databasemanager.h Normal file
View File

@@ -0,0 +1,37 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_DATABASEMANAGER_H_2B75821C555E4D1D83E32B20D683217C
#define FS_DATABASEMANAGER_H_2B75821C555E4D1D83E32B20D683217C
#include "database.h"
class DatabaseManager
{
public:
static bool tableExists(const std::string& table);
static int32_t getDatabaseVersion();
static bool isDatabaseSetup();
static bool optimizeTables();
static bool getDatabaseConfig(const std::string& config, int32_t& value);
static void registerDatabaseConfig(const std::string& config, int32_t value);
};
#endif

101
src/databasetasks.cpp Normal file
View File

@@ -0,0 +1,101 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "databasetasks.h"
#include "tasks.h"
extern Dispatcher g_dispatcher;
void DatabaseTasks::start()
{
db.connect();
ThreadHolder::start();
}
void DatabaseTasks::threadMain()
{
std::unique_lock<std::mutex> taskLockUnique(taskLock, std::defer_lock);
while (getState() != THREAD_STATE_TERMINATED) {
taskLockUnique.lock();
if (tasks.empty()) {
taskSignal.wait(taskLockUnique);
}
if (!tasks.empty()) {
DatabaseTask task = std::move(tasks.front());
tasks.pop_front();
taskLockUnique.unlock();
runTask(task);
} else {
taskLockUnique.unlock();
}
}
}
void DatabaseTasks::addTask(const std::string& query, const std::function<void(DBResult_ptr, bool)>& callback/* = nullptr*/, bool store/* = false*/)
{
bool signal = false;
taskLock.lock();
if (getState() == THREAD_STATE_RUNNING) {
signal = tasks.empty();
tasks.emplace_back(query, callback, store);
}
taskLock.unlock();
if (signal) {
taskSignal.notify_one();
}
}
void DatabaseTasks::runTask(const DatabaseTask& task)
{
bool success;
DBResult_ptr result;
if (task.store) {
result = db.storeQuery(task.query);
success = true;
} else {
result = nullptr;
success = db.executeQuery(task.query);
}
if (task.callback) {
g_dispatcher.addTask(createTask(std::bind(task.callback, result, success)));
}
}
void DatabaseTasks::flush()
{
while (!tasks.empty()) {
runTask(tasks.front());
tasks.pop_front();
}
}
void DatabaseTasks::shutdown()
{
taskLock.lock();
setState(THREAD_STATE_TERMINATED);
flush();
taskLock.unlock();
taskSignal.notify_one();
}

60
src/databasetasks.h Normal file
View File

@@ -0,0 +1,60 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_DATABASETASKS_H_9CBA08E9F5FEBA7275CCEE6560059576
#define FS_DATABASETASKS_H_9CBA08E9F5FEBA7275CCEE6560059576
#include <condition_variable>
#include "thread_holder_base.h"
#include "database.h"
#include "enums.h"
struct DatabaseTask {
DatabaseTask(std::string query, std::function<void(DBResult_ptr, bool)> callback, bool store) :
query(std::move(query)), callback(std::move(callback)), store(store) {}
std::string query;
std::function<void(DBResult_ptr, bool)> callback;
bool store;
};
class DatabaseTasks : public ThreadHolder<DatabaseTasks>
{
public:
DatabaseTasks() = default;
void start();
void flush();
void shutdown();
void addTask(const std::string& query, const std::function<void(DBResult_ptr, bool)>& callback = nullptr, bool store = false);
void threadMain();
private:
void runTask(const DatabaseTask& task);
Database db;
std::thread thread;
std::list<DatabaseTask> tasks;
std::mutex taskLock;
std::condition_variable taskSignal;
};
extern DatabaseTasks g_databaseTasks;
#endif

75
src/definitions.h Normal file
View File

@@ -0,0 +1,75 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963
#define FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963
static constexpr auto STATUS_SERVER_NAME = "Nostalrius";
static constexpr auto STATUS_SERVER_VERSION = "3.0";
static constexpr auto STATUS_SERVER_DEVELOPERS = "Alejandro Mujica";
static constexpr auto CLIENT_VERSION_MIN = 772;
static constexpr auto CLIENT_VERSION_MAX = 772;
static constexpr auto CLIENT_VERSION_STR = "7.72";
#ifndef __FUNCTION__
#define __FUNCTION__ __func__
#endif
#ifndef _USE_MATH_DEFINES
#define _USE_MATH_DEFINES
#endif
#include <cmath>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#define WIN32_LEAN_AND_MEAN
#ifdef _MSC_VER
#ifdef NDEBUG
#define _SECURE_SCL 0
#define HAS_ITERATOR_DEBUGGING 0
#endif
#pragma warning(disable:4127) // conditional expression is constant
#pragma warning(disable:4244) // 'argument' : conversion from 'type1' to 'type2', possible loss of data
#pragma warning(disable:4250) // 'class1' : inherits 'class2::member' via dominance
#pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data
#pragma warning(disable:4351) // new behavior: elements of array will be default initialized
#pragma warning(disable:4458) // declaration hides class member
#endif
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
#ifndef _WIN32_WINNT
// 0x0602: Windows 7
#define _WIN32_WINNT 0x0602
#endif
#endif
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#endif

89
src/depotlocker.cpp Normal file
View File

@@ -0,0 +1,89 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "depotlocker.h"
#include "creature.h"
#include "player.h"
#include "tools.h"
DepotLocker::DepotLocker(uint16_t type) :
Container(type, 30), depotId(0) {}
Attr_ReadValue DepotLocker::readAttr(AttrTypes_t attr, PropStream& propStream)
{
if (attr == ATTR_DEPOT_ID) {
if (!propStream.read<uint16_t>(depotId)) {
return ATTR_READ_ERROR;
}
return ATTR_READ_CONTINUE;
}
return Item::readAttr(attr, propStream);
}
ReturnValue DepotLocker::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor) const
{
const Item* item = thing.getItem();
if (item == nullptr) {
return RETURNVALUE_NOTPOSSIBLE;
}
bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags);
if (!skipLimit) {
int32_t addCount = 0;
if ((item->isStackable() && item->getItemCount() != count)) {
addCount = 1;
}
if (item->getTopParent() != this) {
if (const Container* container = item->getContainer()) {
addCount = container->getItemHoldingCount() + 1;
} else {
addCount = 1;
}
}
if (actor) {
Player* player = actor->getPlayer();
if (player) {
if (getItemHoldingCount() + addCount > player->getMaxDepotItems()) {
return RETURNVALUE_DEPOTISFULL;
}
}
}
}
return Container::queryAdd(index, thing, count, flags, actor);
}
void DepotLocker::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t)
{
if (parent != nullptr) {
parent->postAddNotification(thing, oldParent, index, LINK_PARENT);
}
}
void DepotLocker::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t)
{
if (parent != nullptr) {
parent->postRemoveNotification(thing, newParent, index, LINK_PARENT);
}
}

63
src/depotlocker.h Normal file
View File

@@ -0,0 +1,63 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8
#define FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8
#include "container.h"
class DepotLocker final : public Container
{
public:
explicit DepotLocker(uint16_t type);
DepotLocker* getDepotLocker() final {
return this;
}
const DepotLocker* getDepotLocker() const final {
return this;
}
//serialization
Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final;
uint16_t getDepotId() const {
return depotId;
}
void setDepotId(uint16_t depotId) {
this->depotId = depotId;
}
//cylinder implementations
ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count,
uint32_t flags, Creature* actor = nullptr) const final;
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
bool canRemove() const final {
return false;
}
private:
uint16_t depotId;
};
#endif

382
src/enums.h Normal file
View File

@@ -0,0 +1,382 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE
#define FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE
enum ThreadState {
THREAD_STATE_RUNNING,
THREAD_STATE_CLOSING,
THREAD_STATE_TERMINATED,
};
enum itemAttrTypes : uint32_t {
ITEM_ATTRIBUTE_NONE,
ITEM_ATTRIBUTE_ACTIONID = 1 << 0,
ITEM_ATTRIBUTE_MOVEMENTID = 1 << 1,
ITEM_ATTRIBUTE_DESCRIPTION = 1 << 2,
ITEM_ATTRIBUTE_TEXT = 1 << 3,
ITEM_ATTRIBUTE_DATE = 1 << 4,
ITEM_ATTRIBUTE_WRITER = 1 << 5,
ITEM_ATTRIBUTE_NAME = 1 << 6,
ITEM_ATTRIBUTE_ARTICLE = 1 << 7,
ITEM_ATTRIBUTE_PLURALNAME = 1 << 8,
ITEM_ATTRIBUTE_WEIGHT = 1 << 9,
ITEM_ATTRIBUTE_ATTACK = 1 << 10,
ITEM_ATTRIBUTE_DEFENSE = 1 << 11,
ITEM_ATTRIBUTE_ARMOR = 1 << 12,
ITEM_ATTRIBUTE_SHOOTRANGE = 1 << 13,
ITEM_ATTRIBUTE_OWNER = 1 << 14,
ITEM_ATTRIBUTE_DURATION = 1 << 15,
ITEM_ATTRIBUTE_DECAYSTATE = 1 << 16,
ITEM_ATTRIBUTE_CORPSEOWNER = 1 << 17,
ITEM_ATTRIBUTE_CHARGES = 1 << 18,
ITEM_ATTRIBUTE_FLUIDTYPE = 1 << 19,
ITEM_ATTRIBUTE_DOORID = 1 << 20,
ITEM_ATTRIBUTE_KEYNUMBER = 1 << 21,
ITEM_ATTRIBUTE_KEYHOLENUMBER = 1 << 22,
ITEM_ATTRIBUTE_DOORQUESTNUMBER = 1 << 23,
ITEM_ATTRIBUTE_DOORQUESTVALUE = 1 << 24,
ITEM_ATTRIBUTE_DOORLEVEL = 1 << 25,
ITEM_ATTRIBUTE_CHESTQUESTNUMBER = 1 << 26,
};
enum VipStatus_t : uint8_t {
VIPSTATUS_OFFLINE = 0,
VIPSTATUS_ONLINE = 1,
};
enum OperatingSystem_t : uint8_t {
CLIENTOS_NONE = 0,
CLIENTOS_LINUX = 1,
CLIENTOS_WINDOWS = 2,
CLIENTOS_FLASH = 3,
CLIENTOS_OTCLIENT_LINUX = 10,
CLIENTOS_OTCLIENT_WINDOWS = 11,
CLIENTOS_OTCLIENT_MAC = 12,
};
enum AccountType_t : uint8_t {
ACCOUNT_TYPE_NORMAL = 1,
ACCOUNT_TYPE_TUTOR = 2,
ACCOUNT_TYPE_SENIORTUTOR = 3,
ACCOUNT_TYPE_GAMEMASTER = 4,
ACCOUNT_TYPE_GOD = 5
};
enum RaceType_t : uint8_t {
RACE_NONE,
RACE_VENOM,
RACE_BLOOD,
RACE_UNDEAD,
RACE_FIRE,
};
enum CombatType_t : uint16_t {
COMBAT_NONE = 0,
COMBAT_PHYSICALDAMAGE = 1 << 0,
COMBAT_ENERGYDAMAGE = 1 << 1,
COMBAT_EARTHDAMAGE = 1 << 2,
COMBAT_FIREDAMAGE = 1 << 3,
COMBAT_UNDEFINEDDAMAGE = 1 << 4,
COMBAT_LIFEDRAIN = 1 << 5,
COMBAT_MANADRAIN = 1 << 6,
COMBAT_HEALING = 1 << 7,
COMBAT_COUNT = 9
};
enum CombatParam_t {
COMBAT_PARAM_TYPE,
COMBAT_PARAM_EFFECT,
COMBAT_PARAM_DISTANCEEFFECT,
COMBAT_PARAM_BLOCKSHIELD,
COMBAT_PARAM_BLOCKARMOR,
COMBAT_PARAM_TARGETCASTERORTOPMOST,
COMBAT_PARAM_CREATEITEM,
COMBAT_PARAM_AGGRESSIVE,
COMBAT_PARAM_DISPEL,
COMBAT_PARAM_USECHARGES,
COMBAT_PARAM_DECREASEDAMAGE,
COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE,
};
enum fightMode_t : uint8_t {
FIGHTMODE_ATTACK = 1,
FIGHTMODE_BALANCED = 2,
FIGHTMODE_DEFENSE = 3,
};
enum CallBackParam_t {
CALLBACK_PARAM_LEVELMAGICVALUE,
CALLBACK_PARAM_SKILLVALUE,
CALLBACK_PARAM_TARGETTILE,
CALLBACK_PARAM_TARGETCREATURE,
};
enum ConditionParam_t {
CONDITION_PARAM_OWNER = 1,
CONDITION_PARAM_TICKS = 2,
//CONDITION_PARAM_OUTFIT = 3,
CONDITION_PARAM_HEALTHGAIN = 4,
CONDITION_PARAM_HEALTHTICKS = 5,
CONDITION_PARAM_MANAGAIN = 6,
CONDITION_PARAM_MANATICKS = 7,
CONDITION_PARAM_DELAYED = 8,
CONDITION_PARAM_SPEED = 9,
CONDITION_PARAM_LIGHT_LEVEL = 10,
CONDITION_PARAM_LIGHT_COLOR = 11,
CONDITION_PARAM_SOULGAIN = 12,
CONDITION_PARAM_SOULTICKS = 13,
CONDITION_PARAM_MINVALUE = 14,
CONDITION_PARAM_MAXVALUE = 15,
CONDITION_PARAM_STARTVALUE = 16,
CONDITION_PARAM_TICKINTERVAL = 17,
CONDITION_PARAM_SKILL_MELEE = 19,
CONDITION_PARAM_SKILL_FIST = 20,
CONDITION_PARAM_SKILL_CLUB = 21,
CONDITION_PARAM_SKILL_SWORD = 22,
CONDITION_PARAM_SKILL_AXE = 23,
CONDITION_PARAM_SKILL_DISTANCE = 24,
CONDITION_PARAM_SKILL_SHIELD = 25,
CONDITION_PARAM_SKILL_FISHING = 26,
CONDITION_PARAM_STAT_MAXHITPOINTS = 27,
CONDITION_PARAM_STAT_MAXMANAPOINTS = 28,
// CONDITION_PARAM_STAT_SOULPOINTS = 29,
CONDITION_PARAM_STAT_MAGICPOINTS = 30,
CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT = 31,
CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT = 32,
// CONDITION_PARAM_STAT_SOULPOINTSPERCENT = 33,
CONDITION_PARAM_STAT_MAGICPOINTSPERCENT = 34,
CONDITION_PARAM_PERIODICDAMAGE = 35,
CONDITION_PARAM_SKILL_MELEEPERCENT = 36,
CONDITION_PARAM_SKILL_FISTPERCENT = 37,
CONDITION_PARAM_SKILL_CLUBPERCENT = 38,
CONDITION_PARAM_SKILL_SWORDPERCENT = 39,
CONDITION_PARAM_SKILL_AXEPERCENT = 40,
CONDITION_PARAM_SKILL_DISTANCEPERCENT = 41,
CONDITION_PARAM_SKILL_SHIELDPERCENT = 42,
CONDITION_PARAM_SKILL_FISHINGPERCENT = 43,
// CONDITION_PARAM_BUFF_SPELL = 44,
CONDITION_PARAM_SUBID = 45,
CONDITION_PARAM_FIELD = 46,
CONDITION_PARAM_CYCLE = 47,
CONDITION_PARAM_HIT_DAMAGE = 48,
CONDITION_PARAM_COUNT = 49,
CONDITION_PARAM_MAX_COUNT = 50,
};
enum BlockType_t : uint8_t {
BLOCK_NONE,
BLOCK_DEFENSE,
BLOCK_ARMOR,
BLOCK_IMMUNITY
};
enum skills_t : uint8_t {
SKILL_FIST = 0,
SKILL_CLUB = 1,
SKILL_SWORD = 2,
SKILL_AXE = 3,
SKILL_DISTANCE = 4,
SKILL_SHIELD = 5,
SKILL_FISHING = 6,
SKILL_MAGLEVEL = 7,
SKILL_LEVEL = 8,
SKILL_FIRST = SKILL_FIST,
SKILL_LAST = SKILL_FISHING
};
enum stats_t {
STAT_MAXHITPOINTS,
STAT_MAXMANAPOINTS,
STAT_SOULPOINTS, // unused
STAT_MAGICPOINTS,
STAT_FIRST = STAT_MAXHITPOINTS,
STAT_LAST = STAT_MAGICPOINTS
};
enum formulaType_t {
COMBAT_FORMULA_UNDEFINED,
COMBAT_FORMULA_LEVELMAGIC,
COMBAT_FORMULA_SKILL,
COMBAT_FORMULA_DAMAGE,
};
enum ConditionType_t {
CONDITION_NONE,
CONDITION_POISON = 1 << 0,
CONDITION_FIRE = 1 << 1,
CONDITION_ENERGY = 1 << 2,
CONDITION_HASTE = 1 << 3,
CONDITION_PARALYZE = 1 << 4,
CONDITION_OUTFIT = 1 << 5,
CONDITION_INVISIBLE = 1 << 6,
CONDITION_LIGHT = 1 << 7,
CONDITION_MANASHIELD = 1 << 8,
CONDITION_INFIGHT = 1 << 9,
CONDITION_DRUNK = 1 << 10,
CONDITION_REGENERATION = 1 << 11,
CONDITION_SOUL = 1 << 12,
CONDITION_MUTED = 1 << 13,
CONDITION_CHANNELMUTEDTICKS = 1 << 14,
CONDITION_YELLTICKS = 1 << 15,
CONDITION_ATTRIBUTES = 1 << 16,
CONDITION_EXHAUST = 1 << 17,
CONDITION_PACIFIED = 1 << 18,
CONDITION_AGGRESSIVE = 1 << 19,
};
enum ConditionId_t : int8_t {
CONDITIONID_DEFAULT = -1,
CONDITIONID_COMBAT,
CONDITIONID_HEAD,
CONDITIONID_NECKLACE,
CONDITIONID_BACKPACK,
CONDITIONID_ARMOR,
CONDITIONID_RIGHT,
CONDITIONID_LEFT,
CONDITIONID_LEGS,
CONDITIONID_FEET,
CONDITIONID_RING,
CONDITIONID_AMMO,
};
enum PlayerSex_t : uint8_t {
PLAYERSEX_FEMALE = 0,
PLAYERSEX_MALE = 1,
PLAYERSEX_LAST = PLAYERSEX_MALE
};
enum Vocation_t : uint16_t {
VOCATION_NONE,
VOCATION_SORCERER = 1 << 0,
VOCATION_DRUID = 1 << 1,
VOCATION_PALADIN = 1 << 2,
VOCATION_KNIGHT = 1 << 3,
};
enum ReturnValue {
RETURNVALUE_NOERROR,
RETURNVALUE_NOTPOSSIBLE,
RETURNVALUE_NOTENOUGHROOM,
RETURNVALUE_PLAYERISPZLOCKED,
RETURNVALUE_PLAYERISNOTINVITED,
RETURNVALUE_CANNOTTHROW,
RETURNVALUE_THEREISNOWAY,
RETURNVALUE_DESTINATIONOUTOFREACH,
RETURNVALUE_CREATUREBLOCK,
RETURNVALUE_NOTMOVEABLE,
RETURNVALUE_DROPTWOHANDEDITEM,
RETURNVALUE_BOTHHANDSNEEDTOBEFREE,
RETURNVALUE_CANONLYUSEONEWEAPON,
RETURNVALUE_NEEDEXCHANGE,
RETURNVALUE_CANNOTBEDRESSED,
RETURNVALUE_PUTTHISOBJECTINYOURHAND,
RETURNVALUE_PUTTHISOBJECTINBOTHHANDS,
RETURNVALUE_TOOFARAWAY,
RETURNVALUE_FIRSTGODOWNSTAIRS,
RETURNVALUE_FIRSTGOUPSTAIRS,
RETURNVALUE_CONTAINERNOTENOUGHROOM,
RETURNVALUE_NOTENOUGHCAPACITY,
RETURNVALUE_CANNOTPICKUP,
RETURNVALUE_THISISIMPOSSIBLE,
RETURNVALUE_DEPOTISFULL,
RETURNVALUE_CREATUREDOESNOTEXIST,
RETURNVALUE_CANNOTUSETHISOBJECT,
RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE,
RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE,
RETURNVALUE_YOUAREALREADYTRADING,
RETURNVALUE_THISPLAYERISALREADYTRADING,
RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT,
RETURNVALUE_DIRECTPLAYERSHOOT,
RETURNVALUE_NOTENOUGHLEVEL,
RETURNVALUE_NOTENOUGHMAGICLEVEL,
RETURNVALUE_NOTENOUGHMANA,
RETURNVALUE_NOTENOUGHSOUL,
RETURNVALUE_YOUAREEXHAUSTED,
RETURNVALUE_PLAYERISNOTREACHABLE,
RETURNVALUE_CANONLYUSETHISRUNEONCREATURES,
RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE,
RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER,
RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE,
RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE,
RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE,
RETURNVALUE_YOUCANONLYUSEITONCREATURES,
RETURNVALUE_CREATUREISNOTREACHABLE,
RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS,
RETURNVALUE_YOUNEEDPREMIUMACCOUNT,
RETURNVALUE_YOUNEEDTOLEARNTHISSPELL,
RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL,
RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL,
RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE,
RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE,
RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE,
RETURNVALUE_YOUCANNOTLOGOUTHERE,
RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL,
RETURNVALUE_CANNOTCONJUREITEMHERE,
RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS,
RETURNVALUE_NAMEISTOOAMBIGIOUS,
RETURNVALUE_CANONLYUSEONESHIELD,
RETURNVALUE_NOPARTYMEMBERSINRANGE,
RETURNVALUE_YOUARENOTTHEOWNER,
};
struct Outfit_t {
uint16_t lookType = 0;
uint16_t lookTypeEx = 0;
uint8_t lookHead = 0;
uint8_t lookBody = 0;
uint8_t lookLegs = 0;
uint8_t lookFeet = 0;
};
struct LightInfo {
uint8_t level = 0;
uint8_t color = 0;
constexpr LightInfo() = default;
constexpr LightInfo(uint8_t level, uint8_t color) : level(level), color(color) {}
};
struct CombatDamage
{
CombatType_t type;
int32_t value;
int32_t min;
int32_t max;
CombatDamage()
{
type = COMBAT_NONE;
value = 0;
min = 0;
max = 0;
}
};
#endif

405
src/fileloader.cpp Normal file
View File

@@ -0,0 +1,405 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "fileloader.h"
FileLoader::~FileLoader()
{
if (file) {
fclose(file);
file = nullptr;
}
NodeStruct::clearNet(root);
delete[] buffer;
for (auto& i : cached_data) {
delete[] i.data;
}
}
bool FileLoader::openFile(const char* filename, const char* accept_identifier)
{
file = fopen(filename, "rb");
if (!file) {
lastError = ERROR_CAN_NOT_OPEN;
return false;
}
char identifier[4];
if (fread(identifier, 1, 4, file) < 4) {
fclose(file);
file = nullptr;
lastError = ERROR_EOF;
return false;
}
// The first four bytes must either match the accept identifier or be 0x00000000 (wildcard)
if (memcmp(identifier, accept_identifier, 4) != 0 && memcmp(identifier, "\0\0\0\0", 4) != 0) {
fclose(file);
file = nullptr;
lastError = ERROR_INVALID_FILE_VERSION;
return false;
}
fseek(file, 0, SEEK_END);
int32_t file_size = ftell(file);
cache_size = std::min<uint32_t>(32768, std::max<uint32_t>(file_size / 20, 8192)) & ~0x1FFF;
if (!safeSeek(4)) {
lastError = ERROR_INVALID_FORMAT;
return false;
}
delete root;
root = new NodeStruct();
root->start = 4;
int32_t byte;
if (safeSeek(4) && readByte(byte) && byte == NODE_START) {
return parseNode(root);
}
return false;
}
bool FileLoader::parseNode(NODE node)
{
int32_t byte, pos;
NODE currentNode = node;
while (readByte(byte)) {
currentNode->type = byte;
bool setPropsSize = false;
while (true) {
if (!readByte(byte)) {
return false;
}
bool skipNode = false;
switch (byte) {
case NODE_START: {
//child node start
if (!safeTell(pos)) {
return false;
}
NODE childNode = new NodeStruct();
childNode->start = pos;
currentNode->propsSize = pos - currentNode->start - 2;
currentNode->child = childNode;
setPropsSize = true;
if (!parseNode(childNode)) {
return false;
}
break;
}
case NODE_END: {
//current node end
if (!setPropsSize) {
if (!safeTell(pos)) {
return false;
}
currentNode->propsSize = pos - currentNode->start - 2;
}
if (!readByte(byte)) {
return true;
}
switch (byte) {
case NODE_START: {
//starts next node
if (!safeTell(pos)) {
return false;
}
skipNode = true;
NODE nextNode = new NodeStruct();
nextNode->start = pos;
currentNode->next = nextNode;
currentNode = nextNode;
break;
}
case NODE_END:
return safeTell(pos) && safeSeek(pos);
default:
lastError = ERROR_INVALID_FORMAT;
return false;
}
break;
}
case ESCAPE_CHAR: {
if (!readByte(byte)) {
return false;
}
break;
}
default:
break;
}
if (skipNode) {
break;
}
}
}
return false;
}
const uint8_t* FileLoader::getProps(const NODE node, size_t& size)
{
if (!node) {
return nullptr;
}
if (node->propsSize >= buffer_size) {
delete[] buffer;
while (node->propsSize >= buffer_size) {
buffer_size *= 2;
}
buffer = new uint8_t[buffer_size];
}
//get buffer
if (!readBytes(node->propsSize, node->start + 2)) {
return nullptr;
}
//unscape buffer
size_t j = 0;
bool escaped = false;
for (uint32_t i = 0; i < node->propsSize; ++i, ++j) {
if (buffer[i] == ESCAPE_CHAR) {
//escape char found, skip it and write next
buffer[j] = buffer[++i];
//is neede a displacement for next bytes
escaped = true;
} else if (escaped) {
//perform that displacement
buffer[j] = buffer[i];
}
}
size = j;
return buffer;
}
bool FileLoader::getProps(const NODE node, PropStream& props)
{
size_t size;
if (const uint8_t* a = getProps(node, size)) {
props.init(reinterpret_cast<const char*>(a), size); // does not break strict aliasing
return true;
}
props.init(nullptr, 0);
return false;
}
NODE FileLoader::getChildNode(const NODE parent, uint32_t& type)
{
if (parent) {
NODE child = parent->child;
if (child) {
type = child->type;
}
return child;
}
type = root->type;
return root;
}
NODE FileLoader::getNextNode(const NODE prev, uint32_t& type)
{
if (!prev) {
return NO_NODE;
}
NODE next = prev->next;
if (next) {
type = next->type;
}
return next;
}
inline bool FileLoader::readByte(int32_t& value)
{
if (cache_index == NO_VALID_CACHE) {
lastError = ERROR_CACHE_ERROR;
return false;
}
if (cache_offset >= cached_data[cache_index].size) {
int32_t pos = cache_offset + cached_data[cache_index].base;
int32_t tmp = getCacheBlock(pos);
if (tmp < 0) {
return false;
}
cache_index = tmp;
cache_offset = pos - cached_data[cache_index].base;
if (cache_offset >= cached_data[cache_index].size) {
return false;
}
}
value = cached_data[cache_index].data[cache_offset++];
return true;
}
inline bool FileLoader::readBytes(uint32_t size, int32_t pos)
{
//seek at pos
uint32_t remain = size;
uint8_t* buf = this->buffer;
do {
//prepare cache
uint32_t i = getCacheBlock(pos);
if (i == NO_VALID_CACHE) {
return false;
}
cache_index = i;
cache_offset = pos - cached_data[i].base;
//get maximum read block size and calculate remaining bytes
uint32_t reading = std::min<int32_t>(remain, cached_data[i].size - cache_offset);
remain -= reading;
//read it
memcpy(buf, cached_data[cache_index].data + cache_offset, reading);
//update variables
cache_offset += reading;
buf += reading;
pos += reading;
} while (remain > 0);
return true;
}
inline bool FileLoader::safeSeek(uint32_t pos)
{
uint32_t i = getCacheBlock(pos);
if (i == NO_VALID_CACHE) {
return false;
}
cache_index = i;
cache_offset = pos - cached_data[i].base;
return true;
}
inline bool FileLoader::safeTell(int32_t& pos)
{
if (cache_index == NO_VALID_CACHE) {
lastError = ERROR_CACHE_ERROR;
return false;
}
pos = cached_data[cache_index].base + cache_offset - 1;
return true;
}
inline uint32_t FileLoader::getCacheBlock(uint32_t pos)
{
bool found = false;
uint32_t i, base_pos = pos & ~(cache_size - 1);
for (i = 0; i < CACHE_BLOCKS; i++) {
if (cached_data[i].loaded) {
if (cached_data[i].base == base_pos) {
found = true;
break;
}
}
}
if (!found) {
i = loadCacheBlock(pos);
}
return i;
}
int32_t FileLoader::loadCacheBlock(uint32_t pos)
{
int32_t i, loading_cache = -1, base_pos = pos & ~(cache_size - 1);
for (i = 0; i < CACHE_BLOCKS; i++) {
if (!cached_data[i].loaded) {
loading_cache = i;
break;
}
}
if (loading_cache == -1) {
for (i = 0; i < CACHE_BLOCKS; i++) {
if (std::abs(static_cast<long>(cached_data[i].base) - base_pos) > static_cast<long>(2 * cache_size)) {
loading_cache = i;
break;
}
}
if (loading_cache == -1) {
loading_cache = 0;
}
}
if (cached_data[loading_cache].data == nullptr) {
cached_data[loading_cache].data = new uint8_t[cache_size];
}
cached_data[loading_cache].base = base_pos;
if (fseek(file, cached_data[loading_cache].base, SEEK_SET) != 0) {
lastError = ERROR_SEEK_ERROR;
return -1;
}
uint32_t size = fread(cached_data[loading_cache].data, 1, cache_size, file);
cached_data[loading_cache].size = size;
if (size < (pos - cached_data[loading_cache].base)) {
lastError = ERROR_SEEK_ERROR;
return -1;
}
cached_data[loading_cache].loaded = 1;
return loading_cache;
}

247
src/fileloader.h Normal file
View File

@@ -0,0 +1,247 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_FILELOADER_H_9B663D19E58D42E6BFACFE5B09D7A05E
#define FS_FILELOADER_H_9B663D19E58D42E6BFACFE5B09D7A05E
#include <limits>
#include <vector>
struct NodeStruct;
typedef NodeStruct* NODE;
struct NodeStruct {
uint32_t start = 0;
uint32_t propsSize = 0;
uint32_t type = 0;
NodeStruct* next = nullptr;
NodeStruct* child = nullptr;
static void clearNet(NodeStruct* root) {
if (root) {
clearChild(root);
}
}
private:
static void clearNext(NodeStruct* node) {
NodeStruct* deleteNode = node;
NodeStruct* nextNode;
while (deleteNode) {
if (deleteNode->child) {
clearChild(deleteNode->child);
}
nextNode = deleteNode->next;
delete deleteNode;
deleteNode = nextNode;
}
}
static void clearChild(NodeStruct* node) {
if (node->child) {
clearChild(node->child);
}
if (node->next) {
clearNext(node->next);
}
delete node;
}
};
static constexpr auto NO_NODE = nullptr;
enum FILELOADER_ERRORS {
ERROR_NONE,
ERROR_INVALID_FILE_VERSION,
ERROR_CAN_NOT_OPEN,
ERROR_CAN_NOT_CREATE,
ERROR_EOF,
ERROR_SEEK_ERROR,
ERROR_NOT_OPEN,
ERROR_INVALID_NODE,
ERROR_INVALID_FORMAT,
ERROR_TELL_ERROR,
ERROR_COULDNOTWRITE,
ERROR_CACHE_ERROR,
};
class PropStream;
class FileLoader
{
public:
FileLoader() = default;
~FileLoader();
// non-copyable
FileLoader(const FileLoader&) = delete;
FileLoader& operator=(const FileLoader&) = delete;
bool openFile(const char* filename, const char* identifier);
const uint8_t* getProps(const NODE, size_t& size);
bool getProps(const NODE, PropStream& props);
NODE getChildNode(const NODE parent, uint32_t& type);
NODE getNextNode(const NODE prev, uint32_t& type);
FILELOADER_ERRORS getError() const {
return lastError;
}
protected:
enum SPECIAL_BYTES {
ESCAPE_CHAR = 0xFD,
NODE_START = 0xFE,
NODE_END = 0xFF,
};
bool parseNode(NODE node);
inline bool readByte(int32_t& value);
inline bool readBytes(uint32_t size, int32_t pos);
inline bool safeSeek(uint32_t pos);
inline bool safeTell(int32_t& pos);
protected:
struct cache {
uint8_t* data;
uint32_t loaded;
uint32_t base;
uint32_t size;
};
static constexpr int32_t CACHE_BLOCKS = 3;
cache cached_data[CACHE_BLOCKS] = {};
uint8_t* buffer = new uint8_t[1024];
NODE root = nullptr;
FILE* file = nullptr;
FILELOADER_ERRORS lastError = ERROR_NONE;
uint32_t buffer_size = 1024;
uint32_t cache_size = 0;
static constexpr uint32_t NO_VALID_CACHE = std::numeric_limits<uint32_t>::max();
uint32_t cache_index = NO_VALID_CACHE;
uint32_t cache_offset = NO_VALID_CACHE;
inline uint32_t getCacheBlock(uint32_t pos);
int32_t loadCacheBlock(uint32_t pos);
};
class PropStream
{
public:
void init(const char* a, size_t size) {
p = a;
end = a + size;
}
size_t size() const {
return end - p;
}
template <typename T>
bool read(T& ret) {
if (size() < sizeof(T)) {
return false;
}
memcpy(&ret, p, sizeof(T));
p += sizeof(T);
return true;
}
bool readString(std::string& ret) {
uint16_t strLen;
if (!read<uint16_t>(strLen)) {
return false;
}
if (size() < strLen) {
return false;
}
char* str = new char[strLen + 1];
memcpy(str, p, strLen);
str[strLen] = 0;
ret.assign(str, strLen);
delete[] str;
p += strLen;
return true;
}
bool skip(size_t n) {
if (size() < n) {
return false;
}
p += n;
return true;
}
protected:
const char* p = nullptr;
const char* end = nullptr;
};
class PropWriteStream
{
public:
PropWriteStream() = default;
// non-copyable
PropWriteStream(const PropWriteStream&) = delete;
PropWriteStream& operator=(const PropWriteStream&) = delete;
const char* getStream(size_t& size) const {
size = buffer.size();
return buffer.data();
}
void clear() {
buffer.clear();
}
template <typename T>
void write(T add) {
char* addr = reinterpret_cast<char*>(&add);
std::copy(addr, addr + sizeof(T), std::back_inserter(buffer));
}
void writeString(const std::string& str) {
size_t strLength = str.size();
if (strLength > std::numeric_limits<uint16_t>::max()) {
write<uint16_t>(0);
return;
}
write(static_cast<uint16_t>(strLength));
std::copy(str.begin(), str.end(), std::back_inserter(buffer));
}
protected:
std::vector<char> buffer;
};
#endif

4536
src/game.cpp Normal file

File diff suppressed because it is too large Load Diff

564
src/game.h Normal file
View File

@@ -0,0 +1,564 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_GAME_H_3EC96D67DD024E6093B3BAC29B7A6D7F
#define FS_GAME_H_3EC96D67DD024E6093B3BAC29B7A6D7F
#include "account.h"
#include "combat.h"
#include "commands.h"
#include "groups.h"
#include "map.h"
#include "position.h"
#include "item.h"
#include "container.h"
#include "player.h"
#include "raids.h"
#include "npc.h"
#include "wildcardtree.h"
class ServiceManager;
class Creature;
class Monster;
class Npc;
class CombatInfo;
enum stackPosType_t {
STACKPOS_MOVE,
STACKPOS_LOOK,
STACKPOS_TOPDOWN_ITEM,
STACKPOS_USEITEM,
STACKPOS_USETARGET,
};
enum WorldType_t {
WORLD_TYPE_NO_PVP = 1,
WORLD_TYPE_PVP = 2,
WORLD_TYPE_PVP_ENFORCED = 3,
};
enum GameState_t {
GAME_STATE_STARTUP,
GAME_STATE_INIT,
GAME_STATE_NORMAL,
GAME_STATE_CLOSED,
GAME_STATE_SHUTDOWN,
GAME_STATE_CLOSING,
GAME_STATE_MAINTAIN,
};
enum LightState_t {
LIGHT_STATE_DAY,
LIGHT_STATE_NIGHT,
LIGHT_STATE_SUNSET,
LIGHT_STATE_SUNRISE,
};
struct RuleViolation {
RuleViolation() = default;
RuleViolation(uint32_t _reporterId, const std::string& _text) :
reporterId(_reporterId),
gamemasterId(0),
text(_text),
pending(true)
{
}
uint32_t reporterId;
uint32_t gamemasterId;
std::string text;
bool pending;
};
static constexpr int32_t EVENT_LIGHTINTERVAL = 10000;
static constexpr int32_t EVENT_DECAYINTERVAL = 250;
static constexpr int32_t EVENT_DECAY_BUCKETS = 4;
/**
* Main Game class.
* This class is responsible to control everything that happens
*/
class Game
{
public:
Game() = default;
~Game();
// non-copyable
Game(const Game&) = delete;
Game& operator=(const Game&) = delete;
void start(ServiceManager* manager);
void forceAddCondition(uint32_t creatureId, Condition* condition);
void forceRemoveCondition(uint32_t creatureId, ConditionType_t type);
bool loadMainMap(const std::string& filename);
void loadMap(const std::string& path);
/**
* Get the map size - info purpose only
* \param width width of the map
* \param height height of the map
*/
void getMapDimensions(uint32_t& width, uint32_t& height) const {
width = map.width;
height = map.height;
}
void setWorldType(WorldType_t type);
WorldType_t getWorldType() const {
return worldType;
}
Cylinder* internalGetCylinder(Player* player, const Position& pos) const;
Thing* internalGetThing(Player* player, const Position& pos, int32_t index,
uint32_t spriteId, stackPosType_t type) const;
static void internalGetPosition(Item* item, Position& pos, uint8_t& stackpos);
static std::string getTradeErrorDescription(ReturnValue ret, Item* item);
/**
* Returns a creature based on the unique creature identifier
* \param id is the unique creature id to get a creature pointer to
* \returns A Creature pointer to the creature
*/
Creature* getCreatureByID(uint32_t id);
/**
* Returns a monster based on the unique creature identifier
* \param id is the unique monster id to get a monster pointer to
* \returns A Monster pointer to the monster
*/
Monster* getMonsterByID(uint32_t id);
/**
* Returns a npc based on the unique creature identifier
* \param id is the unique npc id to get a npc pointer to
* \returns A NPC pointer to the npc
*/
Npc* getNpcByID(uint32_t id);
/**
* Returns a player based on the unique creature identifier
* \param id is the unique player id to get a player pointer to
* \returns A Pointer to the player
*/
Player* getPlayerByID(uint32_t id);
/**
* Returns a creature based on a string name identifier
* \param s is the name identifier
* \returns A Pointer to the creature
*/
Creature* getCreatureByName(const std::string& s);
/**
* Returns a npc based on a string name identifier
* \param s is the name identifier
* \returns A Pointer to the npc
*/
Npc* getNpcByName(const std::string& s);
/**
* Returns a player based on a string name identifier
* \param s is the name identifier
* \returns A Pointer to the player
*/
Player* getPlayerByName(const std::string& s);
/**
* Returns a player based on guid
* \returns A Pointer to the player
*/
Player* getPlayerByGUID(const uint32_t& guid);
/**
* Returns a player based on a string name identifier, with support for the "~" wildcard.
* \param s is the name identifier, with or without wildcard
* \param player will point to the found player (if any)
* \return "RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE" or "RETURNVALUE_NAMEISTOOAMBIGIOUS"
*/
ReturnValue getPlayerByNameWildcard(const std::string& s, Player*& player);
/**
* Returns a player based on an account number identifier
* \param acc is the account identifier
* \returns A Pointer to the player
*/
Player* getPlayerByAccount(uint32_t acc);
/* Place Creature on the map without sending out events to the surrounding.
* \param creature Creature to place on the map
* \param pos The position to place the creature
* \param extendedPos If true, the creature will in first-hand be placed 2 tiles away
* \param forced If true, placing the creature will not fail because of obstacles (creatures/items)
*/
bool internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool forced = false);
/**
* Place Creature on the map.
* \param creature Creature to place on the map
* \param pos The position to place the creature
* \param extendedPos If true, the creature will in first-hand be placed 2 tiles away
* \param force If true, placing the creature will not fail because of obstacles (creatures/items)
*/
bool placeCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool force = false);
/**
* Remove Creature from the map.
* Removes the Creature the map
* \param c Creature to remove
*/
bool removeCreature(Creature* creature, bool isLogout = true);
void addCreatureCheck(Creature* creature);
static void removeCreatureCheck(Creature* creature);
size_t getPlayersOnline() const {
return players.size();
}
size_t getMonstersOnline() const {
return monsters.size();
}
size_t getNpcsOnline() const {
return npcs.size();
}
uint32_t getPlayersRecord() const {
return playersRecord;
}
void getWorldLightInfo(LightInfo& lightInfo) const;
ReturnValue internalMoveCreature(Creature* creature, Direction direction, uint32_t flags = 0);
ReturnValue internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags = 0);
ReturnValue 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);
ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index = INDEX_WHEREEVER,
uint32_t flags = 0, bool test = false);
ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index,
uint32_t flags, bool test, uint32_t& remainderCount);
ReturnValue internalRemoveItem(Item* item, int32_t count = -1, bool test = false, uint32_t flags = 0);
ReturnValue internalPlayerAddItem(Player* player, Item* item, bool dropOnMap = true, slots_t slot = CONST_SLOT_WHEREEVER);
/**
* Find an item of a certain type
* \param cylinder to search the item
* \param itemId is the item to remove
* \param subType is the extra type an item can have such as charges/fluidtype, default is -1
* meaning it's not used
* \param depthSearch if true it will check child containers aswell
* \returns A pointer to the item to an item and nullptr if not found
*/
Item* findItemOfType(Cylinder* cylinder, uint16_t itemId,
bool depthSearch = true, int32_t subType = -1) const;
/**
* Remove/Add item(s) with a monetary value
* \param cylinder to remove the money from
* \param money is the amount to remove
* \param flags optional flags to modifiy the default behaviour
* \returns true if the removal was successful
*/
bool removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0);
/**
* Add item(s) with monetary value
* \param cylinder which will receive money
* \param money the amount to give
* \param flags optional flags to modify default behavior
*/
void addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0);
/**
* Transform one item to another type/count
* \param item is the item to transform
* \param newId is the new itemid
* \param newCount is the new count value, use default value (-1) to not change it
* \returns true if the tranformation was successful
*/
Item* transformItem(Item* item, uint16_t newId, int32_t newCount = -1);
/**
* Teleports an object to another position
* \param thing is the object to teleport
* \param newPos is the new position
* \param pushMove force teleport if false
* \param flags optional flags to modify default behavior
* \returns true if the teleportation was successful
*/
ReturnValue internalTeleport(Thing* thing, const Position& newPos, bool pushMove = true, uint32_t flags = 0);
/**
* Turn a creature to a different direction.
* \param creature Creature to change the direction
* \param dir Direction to turn to
*/
bool internalCreatureTurn(Creature* creature, Direction dir);
/**
* Creature wants to say something.
* \param creature Creature pointer
* \param type Type of message
* \param text The text to say
*/
bool internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text,
bool ghostMode, SpectatorVec* listPtr = nullptr, const Position* pos = nullptr);
void loadPlayersRecord();
void checkPlayersRecord();
void kickPlayer(uint32_t playerId, bool displayEffect);
void playerReportBug(uint32_t playerId, const std::string& message);
void playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment);
bool internalStartTrade(Player* player, Player* partner, Item* tradeItem);
void internalCloseTrade(Player* player);
bool playerBroadcastMessage(Player* player, const std::string& text) const;
void broadcastMessage(const std::string& text, MessageClasses type) const;
//Implementation of player invoked events
void playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos,
const Position& toPos, uint8_t count);
void playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, const Position& toPos);
void playerMoveCreature(Player* playerId, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile);
void playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count);
void playerMoveItem(Player* player, const Position& fromPos,
uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder);
void playerMove(uint32_t playerId, Direction direction);
void playerCreatePrivateChannel(uint32_t playerId);
void playerChannelInvite(uint32_t playerId, const std::string& name);
void playerChannelExclude(uint32_t playerId, const std::string& name);
void playerRequestChannels(uint32_t playerId);
void playerOpenChannel(uint32_t playerId, uint16_t channelId);
void playerCloseChannel(uint32_t playerId, uint16_t channelId);
void playerOpenPrivateChannel(uint32_t playerId, std::string& receiver);
void playerReceivePing(uint32_t playerId);
void playerReceivePingBack(uint32_t playerId);
void playerAutoWalk(uint32_t playerId, const std::forward_list<Direction>& listDir);
void playerStopAutoWalk(uint32_t playerId);
void playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos,
uint16_t fromSpriteId, const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId);
void playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, uint8_t index, uint16_t spriteId);
void playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId);
void playerCloseContainer(uint32_t playerId, uint8_t cid);
void playerMoveUpContainer(uint32_t playerId, uint8_t cid);
void playerUpdateContainer(uint32_t playerId, uint8_t cid);
void playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId);
void playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text);
void playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index);
void playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text);
void playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos,
uint32_t tradePlayerId, uint16_t spriteId);
void playerAcceptTrade(uint32_t playerId);
void playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index);
void playerCloseTrade(uint32_t playerId);
void playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId);
void playerFollowCreature(uint32_t playerId, uint32_t creatureId);
void playerCancelAttackAndFollow(uint32_t playerId);
void playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, bool secureMode);
void playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos);
void playerLookInBattleList(uint32_t playerId, uint32_t creatureId);
void playerRequestAddVip(uint32_t playerId, const std::string& name);
void playerRequestRemoveVip(uint32_t playerId, uint32_t guid);
void playerTurn(uint32_t playerId, Direction dir);
void playerRequestOutfit(uint32_t playerId);
void playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type,
const std::string& receiver, const std::string& text);
void playerChangeOutfit(uint32_t playerId, Outfit_t outfit);
void playerInviteToParty(uint32_t playerId, uint32_t invitedId);
void playerJoinParty(uint32_t playerId, uint32_t leaderId);
void playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId);
void playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId);
void playerLeaveParty(uint32_t playerId);
void playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive);
void playerProcessRuleViolationReport(uint32_t playerId, const std::string& name);
void playerCloseRuleViolationReport(uint32_t playerId, const std::string& name);
void playerCancelRuleViolationReport(uint32_t playerId);
void playerReportRuleViolationReport(Player* player, const std::string& text);
void playerContinueRuleViolationReport(Player* player, const std::string& text);
void parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer);
void closeRuleViolationReport(Player* player);
void cancelRuleViolationReport(Player* player);
static void updatePremium(Account& account);
void cleanup();
void shutdown();
void ReleaseCreature(Creature* creature);
void ReleaseItem(Item* item);
bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true,
int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY) const;
bool isSightClear(const Position& fromPos, const Position& toPos, bool sameFloor) const;
void changeSpeed(Creature* creature, int32_t varSpeedDelta);
void internalCreatureChangeOutfit(Creature* creature, const Outfit_t& oufit);
void internalCreatureChangeVisible(Creature* creature, bool visible);
void changeLight(const Creature* creature);
void updateCreatureSkull(const Creature* player);
void updatePlayerShield(Player* player);
GameState_t getGameState() const;
void setGameState(GameState_t newState);
void saveGameState();
//Events
void checkCreatureWalk(uint32_t creatureId);
void updateCreatureWalk(uint32_t creatureId);
void checkCreatureAttack(uint32_t creatureId);
void checkCreatures(size_t index);
void checkLight();
bool combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field);
void combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect);
bool combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage);
bool combatChangeMana(Creature* attacker, Creature* target, int32_t manaChange);
//animation help functions
void addCreatureHealth(const Creature* target);
static void addCreatureHealth(const SpectatorVec& list, const Creature* target);
void addMagicEffect(const Position& pos, uint8_t effect);
static void addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect);
void addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect);
static void addDistanceEffect(const SpectatorVec& list, const Position& fromPos, const Position& toPos, uint8_t effect);
void addAnimatedText(const Position& pos, uint8_t color, const std::string& text);
static void addAnimatedText(const SpectatorVec& list, const Position& pos, uint8_t color, const std::string& text);
void addMonsterSayText(const Position& pos, const std::string& text);
void addCommandTag(char tag);
void resetCommandTag();
void startDecay(Item* item);
int32_t getLightHour() const {
return lightHour;
}
bool loadExperienceStages();
uint64_t getExperienceStage(uint32_t level);
void loadMotdNum();
void saveMotdNum() const;
const std::string& getMotdHash() const { return motdHash; }
uint32_t getMotdNum() const { return motdNum; }
void incrementMotdNum() { motdNum++; }
const std::unordered_map<uint32_t, RuleViolation>& getRuleViolationReports() const { return ruleViolations; }
const std::unordered_map<uint32_t, Player*>& getPlayers() const { return players; }
const std::map<uint32_t, Npc*>& getNpcs() const { return npcs; }
void addPlayer(Player* player);
void removePlayer(Player* player);
void addNpc(Npc* npc);
void removeNpc(Npc* npc);
void addMonster(Monster* npc);
void removeMonster(Monster* npc);
Guild* getGuild(uint32_t id) const;
void addGuild(Guild* guild);
void removeGuild(uint32_t guildId);
void internalRemoveItems(std::vector<Item*> itemList, uint32_t amount, bool stackable);
BedItem* getBedBySleeper(uint32_t guid) const;
void setBedSleeper(BedItem* bed, uint32_t guid);
void removeBedSleeper(uint32_t guid);
Groups groups;
Map map;
Raids raids;
protected:
bool playerSayCommand(Player* player, const std::string& text);
bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text);
void playerWhisper(Player* player, const std::string& text);
bool playerYell(Player* player, const std::string& text);
bool playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, const std::string& text);
void checkDecay();
void internalDecayItem(Item* item);
//list of reported rule violations, for correct channel listing
std::unordered_map<uint32_t, RuleViolation> ruleViolations;
std::unordered_map<uint32_t, Player*> players;
std::unordered_map<std::string, Player*> mappedPlayerNames;
std::unordered_map<uint32_t, Guild*> guilds;
std::map<uint32_t, uint32_t> stages;
std::list<Item*> decayItems[EVENT_DECAY_BUCKETS];
std::list<Creature*> checkCreatureLists[EVENT_CREATURECOUNT];
std::forward_list<Item*> toDecayItems;
std::vector<Creature*> ToReleaseCreatures;
std::vector<Item*> ToReleaseItems;
std::vector<char> commandTags;
size_t lastBucket = 0;
WildcardTreeNode wildcardTree { false };
std::map<uint32_t, Npc*> npcs;
std::map<uint32_t, Monster*> monsters;
//list of items that are in trading state, mapped to the player
std::map<Item*, uint32_t> tradeItems;
std::map<uint32_t, BedItem*> bedSleepersMap;
Commands commands;
static constexpr int32_t LIGHT_LEVEL_DAY = 250;
static constexpr int32_t LIGHT_LEVEL_NIGHT = 40;
static constexpr int32_t SUNSET = 1305;
static constexpr int32_t SUNRISE = 430;
GameState_t gameState = GAME_STATE_NORMAL;
WorldType_t worldType = WORLD_TYPE_PVP;
LightState_t lightState = LIGHT_STATE_DAY;
uint8_t lightLevel = LIGHT_LEVEL_DAY;
int32_t lightHour = SUNRISE + (SUNSET - SUNRISE) / 2;
// (1440 minutes/day)/(3600 seconds/day)*10 seconds event interval
int32_t lightHourDelta = 1400 * 10 / 3600;
ServiceManager* serviceManager = nullptr;
void updatePlayersRecord() const;
uint32_t playersRecord = 0;
std::string motdHash;
uint32_t motdNum = 0;
uint32_t lastStageLevel = 0;
bool stagesEnabled = false;
bool useLastStageLevel = false;
};
#endif

337
src/globalevent.cpp Normal file
View File

@@ -0,0 +1,337 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "configmanager.h"
#include "globalevent.h"
#include "tools.h"
#include "scheduler.h"
#include "pugicast.h"
extern ConfigManager g_config;
GlobalEvents::GlobalEvents() :
scriptInterface("GlobalEvent Interface")
{
scriptInterface.initState();
}
GlobalEvents::~GlobalEvents()
{
clear();
}
void GlobalEvents::clearMap(GlobalEventMap& map)
{
for (const auto& it : map) {
delete it.second;
}
map.clear();
}
void GlobalEvents::clear()
{
g_scheduler.stopEvent(thinkEventId);
thinkEventId = 0;
g_scheduler.stopEvent(timerEventId);
timerEventId = 0;
clearMap(thinkMap);
clearMap(serverMap);
clearMap(timerMap);
scriptInterface.reInitState();
}
Event* GlobalEvents::getEvent(const std::string& nodeName)
{
if (strcasecmp(nodeName.c_str(), "globalevent") != 0) {
return nullptr;
}
return new GlobalEvent(&scriptInterface);
}
bool GlobalEvents::registerEvent(Event* event, const pugi::xml_node&)
{
GlobalEvent* globalEvent = static_cast<GlobalEvent*>(event); //event is guaranteed to be a GlobalEvent
if (globalEvent->getEventType() == GLOBALEVENT_TIMER) {
auto result = timerMap.emplace(globalEvent->getName(), globalEvent);
if (result.second) {
if (timerEventId == 0) {
timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this)));
}
return true;
}
} else if (globalEvent->getEventType() != GLOBALEVENT_NONE) {
auto result = serverMap.emplace(globalEvent->getName(), globalEvent);
if (result.second) {
return true;
}
} else { // think event
auto result = thinkMap.emplace(globalEvent->getName(), globalEvent);
if (result.second) {
if (thinkEventId == 0) {
thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::think, this)));
}
return true;
}
}
std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " << globalEvent->getName() << std::endl;
return false;
}
void GlobalEvents::startup() const
{
execute(GLOBALEVENT_STARTUP);
}
void GlobalEvents::timer()
{
time_t now = time(nullptr);
int64_t nextScheduledTime = std::numeric_limits<int64_t>::max();
auto it = timerMap.begin();
while (it != timerMap.end()) {
GlobalEvent* globalEvent = it->second;
int64_t nextExecutionTime = globalEvent->getNextExecution() - now;
if (nextExecutionTime > 0) {
if (nextExecutionTime < nextScheduledTime) {
nextScheduledTime = nextExecutionTime;
}
++it;
continue;
}
if (!globalEvent->executeEvent()) {
it = timerMap.erase(it);
continue;
}
nextExecutionTime = 86400;
if (nextExecutionTime < nextScheduledTime) {
nextScheduledTime = nextExecutionTime;
}
globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime);
++it;
}
if (nextScheduledTime != std::numeric_limits<int64_t>::max()) {
timerEventId = g_scheduler.addEvent(createSchedulerTask(std::max<int64_t>(1000, nextScheduledTime * 1000),
std::bind(&GlobalEvents::timer, this)));
}
}
void GlobalEvents::think()
{
int64_t now = OTSYS_TIME();
int64_t nextScheduledTime = std::numeric_limits<int64_t>::max();
for (const auto& it : thinkMap) {
GlobalEvent* globalEvent = it.second;
int64_t nextExecutionTime = globalEvent->getNextExecution() - now;
if (nextExecutionTime > 0) {
if (nextExecutionTime < nextScheduledTime) {
nextScheduledTime = nextExecutionTime;
}
continue;
}
if (!globalEvent->executeEvent()) {
std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent->getName() << std::endl;
}
nextExecutionTime = globalEvent->getInterval();
if (nextExecutionTime < nextScheduledTime) {
nextScheduledTime = nextExecutionTime;
}
globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime);
}
if (nextScheduledTime != std::numeric_limits<int64_t>::max()) {
thinkEventId = g_scheduler.addEvent(createSchedulerTask(nextScheduledTime, std::bind(&GlobalEvents::think, this)));
}
}
void GlobalEvents::execute(GlobalEvent_t type) const
{
for (const auto& it : serverMap) {
GlobalEvent* globalEvent = it.second;
if (globalEvent->getEventType() == type) {
globalEvent->executeEvent();
}
}
}
GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type)
{
switch (type) {
case GLOBALEVENT_NONE: return thinkMap;
case GLOBALEVENT_TIMER: return timerMap;
case GLOBALEVENT_STARTUP:
case GLOBALEVENT_SHUTDOWN:
case GLOBALEVENT_RECORD: {
GlobalEventMap retMap;
for (const auto& it : serverMap) {
if (it.second->getEventType() == type) {
retMap[it.first] = it.second;
}
}
return retMap;
}
default: return GlobalEventMap();
}
}
GlobalEvent::GlobalEvent(LuaScriptInterface* interface) : Event(interface) {}
bool GlobalEvent::configureEvent(const pugi::xml_node& node)
{
pugi::xml_attribute nameAttribute = node.attribute("name");
if (!nameAttribute) {
std::cout << "[Error - GlobalEvent::configureEvent] Missing name for a globalevent" << std::endl;
return false;
}
name = nameAttribute.as_string();
eventType = GLOBALEVENT_NONE;
pugi::xml_attribute attr;
if ((attr = node.attribute("time"))) {
std::vector<int32_t> params = vectorAtoi(explodeString(attr.as_string(), ":"));
int32_t hour = params.front();
if (hour < 0 || hour > 23) {
std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl;
return false;
}
interval |= hour << 16;
int32_t min = 0;
int32_t sec = 0;
if (params.size() > 1) {
min = params[1];
if (min < 0 || min > 59) {
std::cout << "[Error - GlobalEvent::configureEvent] Invalid minute \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl;
return false;
}
if (params.size() > 2) {
sec = params[2];
if (sec < 0 || sec > 59) {
std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl;
return false;
}
}
}
time_t current_time = time(nullptr);
tm* timeinfo = localtime(&current_time);
timeinfo->tm_hour = hour;
timeinfo->tm_min = min;
timeinfo->tm_sec = sec;
time_t difference = static_cast<time_t>(difftime(mktime(timeinfo), current_time));
if (difference < 0) {
difference += 86400;
}
nextExecution = current_time + difference;
eventType = GLOBALEVENT_TIMER;
} else if ((attr = node.attribute("type"))) {
const char* value = attr.value();
if (strcasecmp(value, "startup") == 0) {
eventType = GLOBALEVENT_STARTUP;
} else if (strcasecmp(value, "shutdown") == 0) {
eventType = GLOBALEVENT_SHUTDOWN;
} else if (strcasecmp(value, "record") == 0) {
eventType = GLOBALEVENT_RECORD;
} else {
std::cout << "[Error - GlobalEvent::configureEvent] No valid type \"" << attr.as_string() << "\" for globalevent with name " << name << std::endl;
return false;
}
} else if ((attr = node.attribute("interval"))) {
interval = std::max<int32_t>(SCHEDULER_MINTICKS, pugi::cast<int32_t>(attr.value()));
nextExecution = OTSYS_TIME() + interval;
} else {
std::cout << "[Error - GlobalEvent::configureEvent] No interval for globalevent with name " << name << std::endl;
return false;
}
return true;
}
std::string GlobalEvent::getScriptEventName() const
{
switch (eventType) {
case GLOBALEVENT_STARTUP: return "onStartup";
case GLOBALEVENT_SHUTDOWN: return "onShutdown";
case GLOBALEVENT_RECORD: return "onRecord";
case GLOBALEVENT_TIMER: return "onTime";
default: return "onThink";
}
}
bool GlobalEvent::executeRecord(uint32_t current, uint32_t old)
{
//onRecord(current, old)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - GlobalEvent::executeRecord] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
lua_pushnumber(L, current);
lua_pushnumber(L, old);
return scriptInterface->callFunction(2);
}
bool GlobalEvent::executeEvent()
{
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - GlobalEvent::executeEvent] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
int32_t params = 0;
if (eventType == GLOBALEVENT_NONE || eventType == GLOBALEVENT_TIMER) {
lua_pushnumber(L, interval);
params = 1;
}
return scriptInterface->callFunction(params);
}

114
src/globalevent.h Normal file
View File

@@ -0,0 +1,114 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_GLOBALEVENT_H_B3FB9B848EA3474B9AFC326873947E3C
#define FS_GLOBALEVENT_H_B3FB9B848EA3474B9AFC326873947E3C
#include "baseevents.h"
#include "const.h"
enum GlobalEvent_t {
GLOBALEVENT_NONE,
GLOBALEVENT_TIMER,
GLOBALEVENT_STARTUP,
GLOBALEVENT_SHUTDOWN,
GLOBALEVENT_RECORD,
};
class GlobalEvent;
typedef std::map<std::string, GlobalEvent*> GlobalEventMap;
class GlobalEvents final : public BaseEvents
{
public:
GlobalEvents();
~GlobalEvents();
// non-copyable
GlobalEvents(const GlobalEvents&) = delete;
GlobalEvents& operator=(const GlobalEvents&) = delete;
void startup() const;
void timer();
void think();
void execute(GlobalEvent_t type) const;
GlobalEventMap getEventMap(GlobalEvent_t type);
static void clearMap(GlobalEventMap& map);
protected:
std::string getScriptBaseName() const final {
return "globalevents";
}
void clear() final;
Event* getEvent(const std::string& nodeName) final;
bool registerEvent(Event* event, const pugi::xml_node& node) final;
LuaScriptInterface& getScriptInterface() final {
return scriptInterface;
}
LuaScriptInterface scriptInterface;
GlobalEventMap thinkMap, serverMap, timerMap;
int32_t thinkEventId = 0, timerEventId = 0;
};
class GlobalEvent final : public Event
{
public:
explicit GlobalEvent(LuaScriptInterface* interface);
bool configureEvent(const pugi::xml_node& node) final;
bool executeRecord(uint32_t current, uint32_t old);
bool executeEvent();
GlobalEvent_t getEventType() const {
return eventType;
}
const std::string& getName() const {
return name;
}
uint32_t getInterval() const {
return interval;
}
int64_t getNextExecution() const {
return nextExecution;
}
void setNextExecution(int64_t time) {
nextExecution = time;
}
protected:
GlobalEvent_t eventType = GLOBALEVENT_NONE;
std::string getScriptEventName() const final;
std::string name;
int64_t nextExecution = 0;
uint32_t interval = 0;
};
#endif

57
src/groups.cpp Normal file
View File

@@ -0,0 +1,57 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "groups.h"
#include "pugicast.h"
#include "tools.h"
bool Groups::load()
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file("data/XML/groups.xml");
if (!result) {
printXMLError("Error - Groups::load", "data/XML/groups.xml", result);
return false;
}
for (auto groupNode : doc.child("groups").children()) {
Group group;
group.id = pugi::cast<uint32_t>(groupNode.attribute("id").value());
group.name = groupNode.attribute("name").as_string();
group.flags = pugi::cast<uint64_t>(groupNode.attribute("flags").value());
group.access = groupNode.attribute("access").as_bool();
group.maxDepotItems = pugi::cast<uint32_t>(groupNode.attribute("maxdepotitems").value());
group.maxVipEntries = pugi::cast<uint32_t>(groupNode.attribute("maxvipentries").value());
groups.push_back(group);
}
return true;
}
Group* Groups::getGroup(uint16_t id)
{
for (Group& group : groups) {
if (group.id == id) {
return &group;
}
}
return nullptr;
}

41
src/groups.h Normal file
View File

@@ -0,0 +1,41 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_GROUPS_H_EE39438337D148E1983FB79D936DD8F3
#define FS_GROUPS_H_EE39438337D148E1983FB79D936DD8F3
struct Group {
std::string name;
uint64_t flags;
uint32_t maxDepotItems;
uint32_t maxVipEntries;
uint16_t id;
bool access;
};
class Groups {
public:
bool load();
Group* getGroup(uint16_t id);
private:
std::vector<Group> groups;
};
#endif

66
src/guild.cpp Normal file
View File

@@ -0,0 +1,66 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "guild.h"
#include "game.h"
extern Game g_game;
void Guild::addMember(Player* player)
{
membersOnline.push_back(player);
}
void Guild::removeMember(Player* player)
{
membersOnline.remove(player);
if (membersOnline.empty()) {
g_game.removeGuild(id);
delete this;
}
}
GuildRank* Guild::getRankById(uint32_t rankId)
{
for (auto& rank : ranks) {
if (rank.id == rankId) {
return &rank;
}
}
return nullptr;
}
const GuildRank* Guild::getRankByLevel(uint8_t level) const
{
for (const auto& rank : ranks) {
if (rank.level == level) {
return &rank;
}
}
return nullptr;
}
void Guild::addRank(uint32_t rankId, const std::string& rankName, uint8_t level)
{
ranks.emplace_back(rankId, rankName, level);
}

70
src/guild.h Normal file
View File

@@ -0,0 +1,70 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_GUILD_H_C00F0A1D732E4BA88FF62ACBE74D76BC
#define FS_GUILD_H_C00F0A1D732E4BA88FF62ACBE74D76BC
class Player;
struct GuildRank {
uint32_t id;
std::string name;
uint8_t level;
GuildRank(uint32_t id, std::string name, uint8_t level) :
id(id), name(std::move(name)), level(level) {}
};
class Guild
{
public:
Guild(uint32_t id, std::string name) : name(std::move(name)), id(id) {}
void addMember(Player* player);
void removeMember(Player* player);
uint32_t getId() const {
return id;
}
const std::string& getName() const {
return name;
}
const std::list<Player*>& getMembersOnline() const {
return membersOnline;
}
uint32_t getMemberCount() const {
return memberCount;
}
void setMemberCount(uint32_t count) {
memberCount = count;
}
GuildRank* getRankById(uint32_t id);
const GuildRank* getRankByLevel(uint8_t level) const;
void addRank(uint32_t id, const std::string& name, uint8_t level);
private:
std::list<Player*> membersOnline;
std::vector<GuildRank> ranks;
std::string name;
uint32_t id;
uint32_t memberCount = 0;
};
#endif

730
src/house.cpp Normal file
View File

@@ -0,0 +1,730 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "house.h"
#include "iologindata.h"
#include "game.h"
#include "configmanager.h"
#include "bed.h"
extern ConfigManager g_config;
extern Game g_game;
House::House(uint32_t houseId) : id(houseId) {}
void House::addTile(HouseTile* tile)
{
tile->setFlag(TILESTATE_PROTECTIONZONE);
houseTiles.push_back(tile);
}
void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* player/* = nullptr*/)
{
if (updateDatabase && owner != guid) {
Database* db = Database::getInstance();
std::ostringstream query;
query << "UPDATE `houses` SET `owner` = " << guid << ", `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id;
db->executeQuery(query.str());
}
if (isLoaded && owner == guid) {
return;
}
isLoaded = true;
if (owner != 0) {
//send items to depot
if (player) {
transferToDepot(player);
} else {
transferToDepot();
}
for (HouseTile* tile : houseTiles) {
if (const CreatureVector* creatures = tile->getCreatures()) {
for (int32_t i = creatures->size(); --i >= 0;) {
kickPlayer(nullptr, (*creatures)[i]->getPlayer());
}
}
}
// Remove players from beds
for (BedItem* bed : bedsList) {
if (bed->getSleeper() != 0) {
bed->wakeUp(nullptr);
}
}
//clean access lists
owner = 0;
setAccessList(SUBOWNER_LIST, "");
setAccessList(GUEST_LIST, "");
for (Door* door : doorList) {
door->setAccessList("");
}
//reset paid date
paidUntil = 0;
rentWarnings = 0;
}
if (guid != 0) {
std::string name = IOLoginData::getNameByGuid(guid);
if (!name.empty()) {
owner = guid;
ownerName = name;
}
}
updateDoorDescription();
}
void House::updateDoorDescription() const
{
std::ostringstream ss;
if (owner != 0) {
ss << "It belongs to house '" << houseName << "'. " << ownerName << " owns this house.";
} else {
ss << "It belongs to house '" << houseName << "'. Nobody owns this house.";
const int32_t housePrice = getRent();
if (housePrice != -1) {
ss << " It costs " << housePrice * 5 << " gold coins.";
}
}
for (const auto& it : doorList) {
it->setSpecialDescription(ss.str());
}
}
AccessHouseLevel_t House::getHouseAccessLevel(const Player* player)
{
if (!player) {
return HOUSE_OWNER;
}
if (player->hasFlag(PlayerFlag_CanEditHouses)) {
return HOUSE_OWNER;
}
if (player->getGUID() == owner) {
return HOUSE_OWNER;
}
if (subOwnerList.isInList(player)) {
return HOUSE_SUBOWNER;
}
if (guestList.isInList(player)) {
return HOUSE_GUEST;
}
return HOUSE_NOT_INVITED;
}
bool House::kickPlayer(Player* player, Player* target)
{
if (!target) {
return false;
}
HouseTile* houseTile = dynamic_cast<HouseTile*>(target->getTile());
if (!houseTile || houseTile->getHouse() != this) {
return false;
}
if (getHouseAccessLevel(player) < getHouseAccessLevel(target) || target->hasFlag(PlayerFlag_CanEditHouses)) {
return false;
}
Position oldPosition = target->getPosition();
if (g_game.internalTeleport(target, getEntryPosition()) == RETURNVALUE_NOERROR) {
g_game.addMagicEffect(oldPosition, CONST_ME_POFF);
g_game.addMagicEffect(getEntryPosition(), CONST_ME_TELEPORT);
}
return true;
}
void House::setAccessList(uint32_t listId, const std::string& textlist)
{
if (listId == GUEST_LIST) {
guestList.parseList(textlist);
} else if (listId == SUBOWNER_LIST) {
subOwnerList.parseList(textlist);
} else {
Door* door = getDoorByNumber(listId);
if (door) {
door->setAccessList(textlist);
}
// We dont have kick anyone
return;
}
//kick uninvited players
for (HouseTile* tile : houseTiles) {
if (CreatureVector* creatures = tile->getCreatures()) {
for (int32_t i = creatures->size(); --i >= 0;) {
Player* player = (*creatures)[i]->getPlayer();
if (player && !isInvited(player)) {
kickPlayer(nullptr, player);
}
}
}
}
}
bool House::transferToDepot() const
{
if (townId == 0 || owner == 0) {
return false;
}
Player* player = g_game.getPlayerByGUID(owner);
if (player) {
transferToDepot(player);
} else {
Player tmpPlayer(nullptr);
if (!IOLoginData::loadPlayerById(&tmpPlayer, owner)) {
return false;
}
transferToDepot(&tmpPlayer);
IOLoginData::savePlayer(&tmpPlayer);
}
return true;
}
bool House::transferToDepot(Player* player) const
{
if (townId == 0 || owner == 0) {
return false;
}
ItemList moveItemList;
for (HouseTile* tile : houseTiles) {
if (const TileItemVector* items = tile->getItemList()) {
for (Item* item : *items) {
if (item->isPickupable()) {
moveItemList.push_back(item);
} else {
Container* container = item->getContainer();
if (container) {
for (Item* containerItem : container->getItemList()) {
moveItemList.push_back(containerItem);
}
}
}
}
}
}
for (Item* item : moveItemList) {
g_game.internalMoveItem(item->getParent(), player->getDepotLocker(getTownId(), true), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT);
}
return true;
}
bool House::getAccessList(uint32_t listId, std::string& list) const
{
if (listId == GUEST_LIST) {
guestList.getList(list);
return true;
} else if (listId == SUBOWNER_LIST) {
subOwnerList.getList(list);
return true;
}
Door* door = getDoorByNumber(listId);
if (!door) {
return false;
}
return door->getAccessList(list);
}
bool House::isInvited(const Player* player)
{
return getHouseAccessLevel(player) != HOUSE_NOT_INVITED;
}
void House::addDoor(Door* door)
{
door->incrementReferenceCounter();
doorList.push_back(door);
door->setHouse(this);
updateDoorDescription();
}
void House::removeDoor(Door* door)
{
auto it = std::find(doorList.begin(), doorList.end(), door);
if (it != doorList.end()) {
door->decrementReferenceCounter();
doorList.erase(it);
}
}
void House::addBed(BedItem* bed)
{
bedsList.push_back(bed);
bed->setHouse(this);
}
Door* House::getDoorByNumber(uint32_t doorId) const
{
for (Door* door : doorList) {
if (door->getDoorId() == doorId) {
return door;
}
}
return nullptr;
}
Door* House::getDoorByPosition(const Position& pos)
{
for (Door* door : doorList) {
if (door->getPosition() == pos) {
return door;
}
}
return nullptr;
}
bool House::canEditAccessList(uint32_t listId, const Player* player)
{
switch (getHouseAccessLevel(player)) {
case HOUSE_OWNER:
return true;
case HOUSE_SUBOWNER:
return listId == GUEST_LIST;
default:
return false;
}
}
HouseTransferItem* House::getTransferItem()
{
if (transferItem != nullptr) {
return nullptr;
}
transfer_container.setParent(nullptr);
transferItem = HouseTransferItem::createHouseTransferItem(this);
transfer_container.addThing(transferItem);
return transferItem;
}
void House::resetTransferItem()
{
if (transferItem) {
Item* tmpItem = transferItem;
transferItem = nullptr;
transfer_container.setParent(nullptr);
transfer_container.removeThing(tmpItem, tmpItem->getItemCount());
g_game.ReleaseItem(tmpItem);
}
}
HouseTransferItem* HouseTransferItem::createHouseTransferItem(House* house)
{
HouseTransferItem* transferItem = new HouseTransferItem(house);
transferItem->incrementReferenceCounter();
transferItem->setID(ITEM_DOCUMENT_RO);
transferItem->setSubType(1);
std::ostringstream ss;
ss << "It is a house transfer document for '" << house->getName() << "'.";
transferItem->setSpecialDescription(ss.str());
return transferItem;
}
void HouseTransferItem::onTradeEvent(TradeEvents_t event, Player* owner)
{
if (event == ON_TRADE_TRANSFER) {
if (house) {
house->executeTransfer(this, owner);
}
g_game.internalRemoveItem(this, 1);
} else if (event == ON_TRADE_CANCEL) {
if (house) {
house->resetTransferItem();
}
}
}
bool House::executeTransfer(HouseTransferItem* item, Player* newOwner)
{
if (transferItem != item) {
return false;
}
setOwner(newOwner->getGUID());
transferItem = nullptr;
return true;
}
void AccessList::parseList(const std::string& list)
{
playerList.clear();
guildList.clear();
expressionList.clear();
regExList.clear();
this->list = list;
if (list.empty()) {
return;
}
std::istringstream listStream(list);
std::string line;
while (getline(listStream, line)) {
trimString(line);
trim_left(line, '\t');
trim_right(line, '\t');
trimString(line);
if (line.empty() || line.front() == '#' || line.length() > 100) {
continue;
}
toLowerCaseString(line);
std::string::size_type at_pos = line.find("@");
if (at_pos != std::string::npos) {
addGuild(line.substr(at_pos + 1));
} else if (line.find("!") != std::string::npos || line.find("*") != std::string::npos || line.find("?") != std::string::npos) {
addExpression(line);
} else {
addPlayer(line);
}
}
}
void AccessList::addPlayer(const std::string& name)
{
Player* player = g_game.getPlayerByName(name);
if (player) {
playerList.insert(player->getGUID());
} else {
uint32_t guid = IOLoginData::getGuidByName(name);
if (guid != 0) {
playerList.insert(guid);
}
}
}
void AccessList::addGuild(const std::string& name)
{
uint32_t guildId = IOGuild::getGuildIdByName(name);
if (guildId != 0) {
guildList.insert(guildId);
}
}
void AccessList::addExpression(const std::string& expression)
{
if (std::find(expressionList.begin(), expressionList.end(), expression) != expressionList.end()) {
return;
}
std::string outExp;
outExp.reserve(expression.length());
std::string metachars = ".[{}()\\+|^$";
for (const char c : expression) {
if (metachars.find(c) != std::string::npos) {
outExp.push_back('\\');
}
outExp.push_back(c);
}
replaceString(outExp, "*", ".*");
replaceString(outExp, "?", ".?");
try {
if (!outExp.empty()) {
expressionList.push_back(outExp);
if (outExp.front() == '!') {
if (outExp.length() > 1) {
regExList.emplace_front(std::regex(outExp.substr(1)), false);
}
} else {
regExList.emplace_back(std::regex(outExp), true);
}
}
} catch (...) {}
}
bool AccessList::isInList(const Player* player)
{
std::string name = asLowerCaseString(player->getName());
std::cmatch what;
for (const auto& it : regExList) {
if (std::regex_match(name.c_str(), what, it.first)) {
return it.second;
}
}
auto playerIt = playerList.find(player->getGUID());
if (playerIt != playerList.end()) {
return true;
}
const Guild* guild = player->getGuild();
return guild && guildList.find(guild->getId()) != guildList.end();
}
void AccessList::getList(std::string& list) const
{
list = this->list;
}
Door::Door(uint16_t type) : Item(type) {}
Attr_ReadValue Door::readAttr(AttrTypes_t attr, PropStream& propStream)
{
if (attr == ATTR_HOUSEDOORID) {
uint8_t doorId;
if (!propStream.read<uint8_t>(doorId)) {
return ATTR_READ_ERROR;
}
setDoorId(doorId);
return ATTR_READ_CONTINUE;
}
return Item::readAttr(attr, propStream);
}
void Door::setHouse(House* house)
{
if (this->house != nullptr) {
return;
}
this->house = house;
if (!accessList) {
accessList.reset(new AccessList());
}
}
bool Door::canUse(const Player* player)
{
if (!house) {
return true;
}
if (house->getHouseAccessLevel(player) >= HOUSE_SUBOWNER) {
return true;
}
return accessList->isInList(player);
}
void Door::setAccessList(const std::string& textlist)
{
if (!accessList) {
accessList.reset(new AccessList());
}
accessList->parseList(textlist);
}
bool Door::getAccessList(std::string& list) const
{
if (!house) {
return false;
}
accessList->getList(list);
return true;
}
void Door::onRemoved()
{
Item::onRemoved();
if (house) {
house->removeDoor(this);
}
}
House* Houses::getHouseByPlayerId(uint32_t playerId)
{
for (const auto& it : houseMap) {
if (it.second->getOwner() == playerId) {
return it.second;
}
}
return nullptr;
}
bool Houses::loadHousesXML(const std::string& filename)
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(filename.c_str());
if (!result) {
printXMLError("Error - Houses::loadHousesXML", filename, result);
return false;
}
for (auto houseNode : doc.child("houses").children()) {
pugi::xml_attribute houseIdAttribute = houseNode.attribute("houseid");
if (!houseIdAttribute) {
return false;
}
int32_t houseId = pugi::cast<int32_t>(houseIdAttribute.value());
House* house = getHouse(houseId);
if (!house) {
std::cout << "Error: [Houses::loadHousesXML] Unknown house, id = " << houseId << std::endl;
return false;
}
house->setName(houseNode.attribute("name").as_string());
Position entryPos(
pugi::cast<uint16_t>(houseNode.attribute("entryx").value()),
pugi::cast<uint16_t>(houseNode.attribute("entryy").value()),
pugi::cast<uint16_t>(houseNode.attribute("entryz").value())
);
if (entryPos.x == 0 && entryPos.y == 0 && entryPos.z == 0) {
std::cout << "[Warning - Houses::loadHousesXML] House entry not set"
<< " - Name: " << house->getName()
<< " - House id: " << houseId << std::endl;
}
house->setEntryPos(entryPos);
house->setRent(pugi::cast<uint32_t>(houseNode.attribute("rent").value()));
house->setTownId(pugi::cast<uint32_t>(houseNode.attribute("townid").value()));
house->setOwner(0, false);
}
return true;
}
void Houses::payHouses(RentPeriod_t rentPeriod) const
{
if (rentPeriod == RENTPERIOD_NEVER) {
return;
}
time_t currentTime = time(nullptr);
for (const auto& it : houseMap) {
House* house = it.second;
if (house->getOwner() == 0) {
continue;
}
const uint32_t rent = house->getRent();
if (rent == 0 || house->getPaidUntil() > currentTime) {
continue;
}
const uint32_t ownerId = house->getOwner();
Town* town = g_game.map.towns.getTown(house->getTownId());
if (!town) {
continue;
}
Player player(nullptr);
if (!IOLoginData::loadPlayerById(&player, ownerId)) {
// Player doesn't exist, reset house owner
house->setOwner(0);
continue;
}
if (g_game.removeMoney(player.getDepotLocker(house->getTownId(), true), house->getRent(), FLAG_NOLIMIT)) {
time_t paidUntil = currentTime;
switch (rentPeriod) {
case RENTPERIOD_DAILY:
paidUntil += 24 * 60 * 60;
break;
case RENTPERIOD_WEEKLY:
paidUntil += 24 * 60 * 60 * 7;
break;
case RENTPERIOD_MONTHLY:
paidUntil += 24 * 60 * 60 * 30;
break;
case RENTPERIOD_YEARLY:
paidUntil += 24 * 60 * 60 * 365;
break;
default:
break;
}
house->setPaidUntil(paidUntil);
} else {
if (house->getPayRentWarnings() < 7) {
int32_t daysLeft = 7 - house->getPayRentWarnings();
Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED);
std::string period;
switch (rentPeriod) {
case RENTPERIOD_DAILY:
period = "daily";
break;
case RENTPERIOD_WEEKLY:
period = "weekly";
break;
case RENTPERIOD_MONTHLY:
period = "monthly";
break;
case RENTPERIOD_YEARLY:
period = "annual";
break;
default:
break;
}
std::ostringstream ss;
ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house.";
letter->setText(ss.str());
g_game.internalAddItem(player.getDepotLocker(house->getTownId(), true), letter, INDEX_WHEREEVER, FLAG_NOLIMIT);
house->setPayRentWarnings(house->getPayRentWarnings() + 1);
} else {
house->setOwner(0, true, &player);
}
}
IOLoginData::savePlayer(&player);
}
}

315
src/house.h Normal file
View File

@@ -0,0 +1,315 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_HOUSE_H_EB9732E7771A438F9CD0EFA8CB4C58C4
#define FS_HOUSE_H_EB9732E7771A438F9CD0EFA8CB4C58C4
#include <regex>
#include "container.h"
#include "housetile.h"
#include "position.h"
class House;
class BedItem;
class Player;
class AccessList
{
public:
void parseList(const std::string& list);
void addPlayer(const std::string& name);
void addGuild(const std::string& name);
void addExpression(const std::string& expression);
bool isInList(const Player* player);
void getList(std::string& list) const;
private:
std::string list;
std::unordered_set<uint32_t> playerList;
std::unordered_set<uint32_t> guildList; // TODO: include ranks
std::list<std::string> expressionList;
std::list<std::pair<std::regex, bool>> regExList;
};
class Door final : public Item
{
public:
explicit Door(uint16_t type);
// non-copyable
Door(const Door&) = delete;
Door& operator=(const Door&) = delete;
Door* getDoor() final {
return this;
}
const Door* getDoor() const final {
return this;
}
House* getHouse() {
return house;
}
//serialization
Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final;
void serializeAttr(PropWriteStream&) const final {}
void setDoorId(uint32_t doorId) {
setIntAttr(ITEM_ATTRIBUTE_DOORID, doorId);
}
uint32_t getDoorId() const {
return getIntAttr(ITEM_ATTRIBUTE_DOORID);
}
bool canUse(const Player* player);
void setAccessList(const std::string& textlist);
bool getAccessList(std::string& list) const;
void onRemoved() final;
protected:
void setHouse(House* house);
private:
House* house = nullptr;
std::unique_ptr<AccessList> accessList;
friend class House;
};
enum AccessList_t {
GUEST_LIST = 0x100,
SUBOWNER_LIST = 0x101,
};
enum AccessHouseLevel_t {
HOUSE_NOT_INVITED = 0,
HOUSE_GUEST = 1,
HOUSE_SUBOWNER = 2,
HOUSE_OWNER = 3,
};
typedef std::list<HouseTile*> HouseTileList;
typedef std::list<BedItem*> HouseBedItemList;
class HouseTransferItem final : public Item
{
public:
static HouseTransferItem* createHouseTransferItem(House* house);
explicit HouseTransferItem(House* house) : Item(0), house(house) {}
void onTradeEvent(TradeEvents_t event, Player* owner) final;
bool canTransform() const final {
return false;
}
protected:
House* house;
};
class House
{
public:
explicit House(uint32_t houseId);
void addTile(HouseTile* tile);
void updateDoorDescription() const;
bool canEditAccessList(uint32_t listId, const Player* player);
// listId special values:
// GUEST_LIST guest list
// SUBOWNER_LIST subowner list
void setAccessList(uint32_t listId, const std::string& textlist);
bool getAccessList(uint32_t listId, std::string& list) const;
bool isInvited(const Player* player);
AccessHouseLevel_t getHouseAccessLevel(const Player* player);
bool kickPlayer(Player* player, Player* target);
void setEntryPos(Position pos) {
posEntry = pos;
}
const Position& getEntryPosition() const {
return posEntry;
}
void setName(std::string houseName) {
this->houseName = houseName;
}
const std::string& getName() const {
return houseName;
}
void setOwner(uint32_t guid, bool updateDatabase = true, Player* player = nullptr);
uint32_t getOwner() const {
return owner;
}
void setPaidUntil(time_t paid) {
paidUntil = paid;
}
time_t getPaidUntil() const {
return paidUntil;
}
void setRent(uint32_t rent) {
this->rent = rent;
}
uint32_t getRent() const {
return rent;
}
void setPayRentWarnings(uint32_t warnings) {
rentWarnings = warnings;
}
uint32_t getPayRentWarnings() const {
return rentWarnings;
}
void setTownId(uint32_t townId) {
this->townId = townId;
}
uint32_t getTownId() const {
return townId;
}
uint32_t getId() const {
return id;
}
void addDoor(Door* door);
void removeDoor(Door* door);
Door* getDoorByNumber(uint32_t doorId) const;
Door* getDoorByPosition(const Position& pos);
HouseTransferItem* getTransferItem();
void resetTransferItem();
bool executeTransfer(HouseTransferItem* item, Player* player);
const HouseTileList& getTiles() const {
return houseTiles;
}
const std::list<Door*>& getDoors() const {
return doorList;
}
void addBed(BedItem* bed);
const HouseBedItemList& getBeds() const {
return bedsList;
}
uint32_t getBedCount() {
return static_cast<uint32_t>(std::ceil(bedsList.size() / 2.)); //each bed takes 2 sqms of space, ceil is just for bad maps
}
private:
bool transferToDepot() const;
bool transferToDepot(Player* player) const;
AccessList guestList;
AccessList subOwnerList;
Container transfer_container{ITEM_LOCKER1};
HouseTileList houseTiles;
std::list<Door*> doorList;
HouseBedItemList bedsList;
std::string houseName;
std::string ownerName;
HouseTransferItem* transferItem = nullptr;
time_t paidUntil = 0;
uint32_t id;
uint32_t owner = 0;
uint32_t rentWarnings = 0;
uint32_t rent = 0;
uint32_t townId = 0;
Position posEntry = {};
bool isLoaded = false;
};
typedef std::map<uint32_t, House*> HouseMap;
enum RentPeriod_t {
RENTPERIOD_DAILY,
RENTPERIOD_WEEKLY,
RENTPERIOD_MONTHLY,
RENTPERIOD_YEARLY,
RENTPERIOD_NEVER,
};
class Houses
{
public:
Houses() = default;
~Houses() {
for (const auto& it : houseMap) {
delete it.second;
}
}
// non-copyable
Houses(const Houses&) = delete;
Houses& operator=(const Houses&) = delete;
House* addHouse(uint32_t id) {
auto it = houseMap.find(id);
if (it != houseMap.end()) {
return it->second;
}
House* house = new House(id);
houseMap[id] = house;
return house;
}
House* getHouse(uint32_t houseId) {
auto it = houseMap.find(houseId);
if (it == houseMap.end()) {
return nullptr;
}
return it->second;
}
House* getHouseByPlayerId(uint32_t playerId);
bool loadHousesXML(const std::string& filename);
void payHouses(RentPeriod_t rentPeriod) const;
const HouseMap& getHouses() const {
return houseMap;
}
private:
HouseMap houseMap;
};
#endif

122
src/housetile.cpp Normal file
View File

@@ -0,0 +1,122 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "housetile.h"
#include "house.h"
#include "game.h"
extern Game g_game;
HouseTile::HouseTile(int32_t x, int32_t y, int32_t z, House* house) :
DynamicTile(x, y, z), house(house) {}
void HouseTile::addThing(int32_t index, Thing* thing)
{
Tile::addThing(index, thing);
if (!thing->getParent()) {
return;
}
if (Item* item = thing->getItem()) {
updateHouse(item);
}
}
void HouseTile::internalAddThing(uint32_t index, Thing* thing)
{
Tile::internalAddThing(index, thing);
if (!thing->getParent()) {
return;
}
if (Item* item = thing->getItem()) {
updateHouse(item);
}
}
void HouseTile::updateHouse(Item* item)
{
if (item->getParent() != this) {
return;
}
Door* door = item->getDoor();
if (door) {
if (door->getDoorId() != 0) {
house->addDoor(door);
}
} else {
BedItem* bed = item->getBed();
if (bed) {
house->addBed(bed);
}
}
}
ReturnValue HouseTile::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor/* = nullptr*/) const
{
if (const Creature* creature = thing.getCreature()) {
if (const Player* player = creature->getPlayer()) {
if (!house->isInvited(player)) {
return RETURNVALUE_PLAYERISNOTINVITED;
}
} else {
return RETURNVALUE_NOTPOSSIBLE;
}
} else if (thing.getItem() && actor) {
Player* actorPlayer = actor->getPlayer();
if (!house->isInvited(actorPlayer)) {
return RETURNVALUE_CANNOTTHROW;
}
}
return Tile::queryAdd(index, thing, count, flags, actor);
}
Tile* HouseTile::queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags)
{
if (const Creature* creature = thing.getCreature()) {
if (const Player* player = creature->getPlayer()) {
if (!house->isInvited(player)) {
const Position& entryPos = house->getEntryPosition();
Tile* destTile = g_game.map.getTile(entryPos);
if (!destTile) {
std::cout << "Error: [HouseTile::queryDestination] House entry not correct"
<< " - Name: " << house->getName()
<< " - House id: " << house->getId()
<< " - Tile not found: " << entryPos << std::endl;
destTile = g_game.map.getTile(player->getTemplePosition());
if (!destTile) {
destTile = &(Tile::nullptr_tile);
}
}
index = -1;
*destItem = nullptr;
return destTile;
}
}
}
return Tile::queryDestination(index, thing, destItem, flags);
}

52
src/housetile.h Normal file
View File

@@ -0,0 +1,52 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_HOUSETILE_H_57D59BEC1CE741D9B142BFC54634505B
#define FS_HOUSETILE_H_57D59BEC1CE741D9B142BFC54634505B
#include "tile.h"
class House;
class HouseTile final : public DynamicTile
{
public:
HouseTile(int32_t x, int32_t y, int32_t z, House* house);
//cylinder implementations
ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count,
uint32_t flags, Creature* actor = nullptr) const final;
Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem,
uint32_t& flags) final;
void addThing(int32_t index, Thing* thing) final;
void internalAddThing(uint32_t index, Thing* thing) final;
House* getHouse() {
return house;
}
private:
void updateHouse(Item* item);
House* house;
};
#endif

57
src/ioguild.cpp Normal file
View File

@@ -0,0 +1,57 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "ioguild.h"
#include "database.h"
uint32_t IOGuild::getGuildIdByName(const std::string& name)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `id` FROM `guilds` WHERE `name` = " << db->escapeString(name);
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return 0;
}
return result->getNumber<uint32_t>("id");
}
void IOGuild::getWarList(uint32_t guildId, GuildWarList& guildWarList)
{
std::ostringstream query;
query << "SELECT `guild1`, `guild2` FROM `guild_wars` WHERE (`guild1` = " << guildId << " OR `guild2` = " << guildId << ") AND `ended` = 0 AND `status` = 1";
DBResult_ptr result = Database::getInstance()->storeQuery(query.str());
if (!result) {
return;
}
do {
uint32_t guild1 = result->getNumber<uint32_t>("guild1");
if (guildId != guild1) {
guildWarList.push_back(guild1);
} else {
guildWarList.push_back(result->getNumber<uint32_t>("guild2"));
}
} while (result->next());
}

32
src/ioguild.h Normal file
View File

@@ -0,0 +1,32 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF
#define FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF
typedef std::vector<uint32_t> GuildWarList;
class IOGuild
{
public:
static uint32_t getGuildIdByName(const std::string& name);
static void getWarList(uint32_t guildId, GuildWarList& guildWarList);
};
#endif

950
src/iologindata.cpp Normal file
View File

@@ -0,0 +1,950 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "iologindata.h"
#include "configmanager.h"
#include "game.h"
extern ConfigManager g_config;
extern Game g_game;
Account IOLoginData::loadAccount(uint32_t accno)
{
Account account;
std::ostringstream query;
query << "SELECT `id`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `id` = " << accno;
DBResult_ptr result = Database::getInstance()->storeQuery(query.str());
if (!result) {
return account;
}
account.id = result->getNumber<uint32_t>("id");
account.accountType = static_cast<AccountType_t>(result->getNumber<int32_t>("type"));
account.premiumDays = result->getNumber<uint16_t>("premdays");
account.lastDay = result->getNumber<time_t>("lastday");
return account;
}
bool IOLoginData::saveAccount(const Account& acc)
{
std::ostringstream query;
query << "UPDATE `accounts` SET `premdays` = " << acc.premiumDays << ", `lastday` = " << acc.lastDay << " WHERE `id` = " << acc.id;
return Database::getInstance()->executeQuery(query.str());
}
bool IOLoginData::loginserverAuthentication(uint32_t accountNumber, const std::string& password, Account& account)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `id`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `id` = " << accountNumber;
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return false;
}
if (transformToSHA1(password) != result->getString("password")) {
return false;
}
account.id = result->getNumber<uint32_t>("id");
account.accountType = static_cast<AccountType_t>(result->getNumber<int32_t>("type"));
account.premiumDays = result->getNumber<uint16_t>("premdays");
account.lastDay = result->getNumber<time_t>("lastday");
query.str(std::string());
query << "SELECT `name`, `deletion` FROM `players` WHERE `account_id` = " << account.id;
result = db->storeQuery(query.str());
if (result) {
do {
if (result->getNumber<uint64_t>("deletion") == 0) {
account.characters.push_back(result->getString("name"));
}
} while (result->next());
std::sort(account.characters.begin(), account.characters.end());
}
return true;
}
uint32_t IOLoginData::gameworldAuthentication(uint32_t accountNumber, const std::string& password, std::string& characterName)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `id`, `password` FROM `accounts` WHERE `id` = " << accountNumber;
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return 0;
}
if (transformToSHA1(password) != result->getString("password")) {
return 0;
}
uint32_t accountId = result->getNumber<uint32_t>("id");
query.str(std::string());
query << "SELECT `account_id`, `name`, `deletion` FROM `players` WHERE `name` = " << db->escapeString(characterName);
result = db->storeQuery(query.str());
if (!result) {
return 0;
}
if (result->getNumber<uint32_t>("account_id") != accountId || result->getNumber<uint64_t>("deletion") != 0) {
return 0;
}
characterName = result->getString("name");
return accountId;
}
AccountType_t IOLoginData::getAccountType(uint32_t accountId)
{
std::ostringstream query;
query << "SELECT `type` FROM `accounts` WHERE `id` = " << accountId;
DBResult_ptr result = Database::getInstance()->storeQuery(query.str());
if (!result) {
return ACCOUNT_TYPE_NORMAL;
}
return static_cast<AccountType_t>(result->getNumber<uint16_t>("type"));
}
void IOLoginData::setAccountType(uint32_t accountId, AccountType_t accountType)
{
std::ostringstream query;
query << "UPDATE `accounts` SET `type` = " << static_cast<uint16_t>(accountType) << " WHERE `id` = " << accountId;
Database::getInstance()->executeQuery(query.str());
}
void IOLoginData::updateOnlineStatus(uint32_t guid, bool login)
{
if (g_config.getBoolean(ConfigManager::ALLOW_CLONES)) {
return;
}
std::ostringstream query;
if (login) {
query << "INSERT INTO `players_online` VALUES (" << guid << ')';
} else {
query << "DELETE FROM `players_online` WHERE `player_id` = " << guid;
}
Database::getInstance()->executeQuery(query.str());
}
bool IOLoginData::preloadPlayer(Player* player, const std::string& name)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `id`, `account_id`, `group_id`, `deletion`, (SELECT `type` FROM `accounts` WHERE `accounts`.`id` = `account_id`) AS `account_type`";
if (!g_config.getBoolean(ConfigManager::FREE_PREMIUM)) {
query << ", (SELECT `premdays` FROM `accounts` WHERE `accounts`.`id` = `account_id`) AS `premium_days`";
}
query << " FROM `players` WHERE `name` = " << db->escapeString(name);
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return false;
}
if (result->getNumber<uint64_t>("deletion") != 0) {
return false;
}
player->setGUID(result->getNumber<uint32_t>("id"));
Group* group = g_game.groups.getGroup(result->getNumber<uint16_t>("group_id"));
if (!group) {
std::cout << "[Error - IOLoginData::preloadPlayer] " << player->name << " has Group ID " << result->getNumber<uint16_t>("group_id") << " which doesn't exist." << std::endl;
return false;
}
player->setGroup(group);
player->accountNumber = result->getNumber<uint32_t>("account_id");
player->accountType = static_cast<AccountType_t>(result->getNumber<uint16_t>("account_type"));
if (!g_config.getBoolean(ConfigManager::FREE_PREMIUM)) {
player->premiumDays = result->getNumber<uint16_t>("premium_days");
} else {
player->premiumDays = std::numeric_limits<uint16_t>::max();
}
return true;
}
bool IOLoginData::loadPlayerById(Player* player, uint32_t id)
{
std::ostringstream query;
query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries` FROM `players` WHERE `id` = " << id;
return loadPlayer(player, Database::getInstance()->storeQuery(query.str()));
}
bool IOLoginData::loadPlayerByName(Player* player, const std::string& name)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries` FROM `players` WHERE `name` = " << db->escapeString(name);
return loadPlayer(player, db->storeQuery(query.str()));
}
bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
{
if (!result) {
return false;
}
Database* db = Database::getInstance();
uint32_t accno = result->getNumber<uint32_t>("account_id");
Account acc = loadAccount(accno);
player->setGUID(result->getNumber<uint32_t>("id"));
player->name = result->getString("name");
player->accountNumber = accno;
player->accountType = acc.accountType;
if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) {
player->premiumDays = std::numeric_limits<uint16_t>::max();
} else {
player->premiumDays = acc.premiumDays;
}
Group* group = g_game.groups.getGroup(result->getNumber<uint16_t>("group_id"));
if (!group) {
std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Group ID " << result->getNumber<uint16_t>("group_id") << " which doesn't exist" << std::endl;
return false;
}
player->setGroup(group);
player->bankBalance = result->getNumber<uint64_t>("balance");
player->setSex(static_cast<PlayerSex_t>(result->getNumber<uint16_t>("sex")));
player->level = std::max<uint32_t>(1, result->getNumber<uint32_t>("level"));
uint64_t experience = result->getNumber<uint64_t>("experience");
uint64_t currExpCount = Player::getExpForLevel(player->level);
uint64_t nextExpCount = Player::getExpForLevel(player->level + 1);
if (experience < currExpCount || experience > nextExpCount) {
experience = currExpCount;
}
player->experience = experience;
if (currExpCount < nextExpCount) {
player->levelPercent = Player::getPercentLevel(player->experience - currExpCount, nextExpCount - currExpCount);
} else {
player->levelPercent = 0;
}
player->soul = result->getNumber<uint16_t>("soul");
player->capacity = std::max<uint32_t>(400, result->getNumber<uint32_t>("cap")) * 100;
player->blessings = result->getNumber<uint16_t>("blessings");
unsigned long conditionsSize;
const char* conditions = result->getStream("conditions", conditionsSize);
PropStream propStream;
propStream.init(conditions, conditionsSize);
Condition* condition = Condition::createCondition(propStream);
while (condition) {
if (condition->unserialize(propStream)) {
player->storedConditionList.push_front(condition);
} else {
delete condition;
}
condition = Condition::createCondition(propStream);
}
if (!player->setVocation(result->getNumber<uint16_t>("vocation"))) {
std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Vocation ID " << result->getNumber<uint16_t>("vocation") << " which doesn't exist" << std::endl;
return false;
}
player->mana = result->getNumber<uint32_t>("mana");
player->manaMax = result->getNumber<uint32_t>("manamax");
player->magLevel = result->getNumber<uint32_t>("maglevel");
uint64_t nextManaCount = player->vocation->getReqMana(player->magLevel + 1);
uint64_t manaSpent = result->getNumber<uint64_t>("manaspent");
if (manaSpent > nextManaCount) {
manaSpent = 0;
}
player->manaSpent = manaSpent;
player->magLevelPercent = Player::getPercentLevel(player->manaSpent, nextManaCount);
player->health = result->getNumber<int32_t>("health");
player->healthMax = result->getNumber<int32_t>("healthmax");
player->defaultOutfit.lookType = result->getNumber<uint16_t>("looktype");
player->defaultOutfit.lookHead = result->getNumber<uint16_t>("lookhead");
player->defaultOutfit.lookBody = result->getNumber<uint16_t>("lookbody");
player->defaultOutfit.lookLegs = result->getNumber<uint16_t>("looklegs");
player->defaultOutfit.lookFeet = result->getNumber<uint16_t>("lookfeet");
player->currentOutfit = player->defaultOutfit;
if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) {
player->playerKillerEnd = result->getNumber<time_t>("skulltime");
uint16_t skull = result->getNumber<uint16_t>("skull");
if (skull == SKULL_RED) {
player->skull = SKULL_RED;
}
if (player->playerKillerEnd == 0) {
player->skull = SKULL_NONE;
}
}
player->loginPosition.x = result->getNumber<uint16_t>("posx");
player->loginPosition.y = result->getNumber<uint16_t>("posy");
player->loginPosition.z = result->getNumber<uint16_t>("posz");
player->lastLoginSaved = result->getNumber<time_t>("lastlogin");
player->lastLogout = result->getNumber<time_t>("lastlogout");
Town* town = g_game.map.towns.getTown(result->getNumber<uint32_t>("town_id"));
if (!town) {
std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Town ID " << result->getNumber<uint32_t>("town_id") << " which doesn't exist" << std::endl;
return false;
}
player->town = town;
const Position& loginPos = player->loginPosition;
if (loginPos.x == 0 && loginPos.y == 0 && loginPos.z == 0) {
player->loginPosition = player->getTemplePosition();
}
static const std::string skillNames[] = {"skill_fist", "skill_club", "skill_sword", "skill_axe", "skill_dist", "skill_shielding", "skill_fishing"};
static const std::string skillNameTries[] = {"skill_fist_tries", "skill_club_tries", "skill_sword_tries", "skill_axe_tries", "skill_dist_tries", "skill_shielding_tries", "skill_fishing_tries"};
static constexpr size_t size = sizeof(skillNames) / sizeof(std::string);
for (uint8_t i = 0; i < size; ++i) {
uint16_t skillLevel = result->getNumber<uint16_t>(skillNames[i]);
uint64_t skillTries = result->getNumber<uint64_t>(skillNameTries[i]);
uint64_t nextSkillTries = player->vocation->getReqSkillTries(i, skillLevel + 1);
if (skillTries > nextSkillTries) {
skillTries = 0;
}
player->skills[i].level = skillLevel;
player->skills[i].tries = skillTries;
player->skills[i].percent = Player::getPercentLevel(skillTries, nextSkillTries);
}
std::ostringstream query;
query << "SELECT `date` FROM `player_murders` WHERE `player_id` = " << player->getGUID() << " ORDER BY `date` ASC";
if ((result = db->storeQuery(query.str()))) {
do {
player->murderTimeStamps.push_back(result->getNumber<time_t>("date"));
} while (result->next());
}
query.str(std::string());
query << "SELECT `guild_id`, `rank_id`, `nick` FROM `guild_membership` WHERE `player_id` = " << player->getGUID();
if ((result = db->storeQuery(query.str()))) {
uint32_t guildId = result->getNumber<uint32_t>("guild_id");
uint32_t playerRankId = result->getNumber<uint32_t>("rank_id");
player->guildNick = result->getString("nick");
Guild* guild = g_game.getGuild(guildId);
if (!guild) {
query.str(std::string());
query << "SELECT `name` FROM `guilds` WHERE `id` = " << guildId;
if ((result = db->storeQuery(query.str()))) {
guild = new Guild(guildId, result->getString("name"));
g_game.addGuild(guild);
query.str(std::string());
query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `guild_id` = " << guildId;
if ((result = db->storeQuery(query.str()))) {
do {
guild->addRank(result->getNumber<uint32_t>("id"), result->getString("name"), result->getNumber<uint16_t>("level"));
} while (result->next());
}
}
}
if (guild) {
player->guild = guild;
const GuildRank* rank = guild->getRankById(playerRankId);
if (!rank) {
query.str(std::string());
query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `id` = " << playerRankId;
if ((result = db->storeQuery(query.str()))) {
guild->addRank(result->getNumber<uint32_t>("id"), result->getString("name"), result->getNumber<uint16_t>("level"));
}
rank = guild->getRankById(playerRankId);
if (!rank) {
player->guild = nullptr;
}
}
player->guildRank = rank;
IOGuild::getWarList(guildId, player->guildWarList);
query.str(std::string());
query << "SELECT COUNT(*) AS `members` FROM `guild_membership` WHERE `guild_id` = " << guildId;
if ((result = db->storeQuery(query.str()))) {
guild->setMemberCount(result->getNumber<uint32_t>("members"));
}
}
}
query.str(std::string());
query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID();
if ((result = db->storeQuery(query.str()))) {
do {
player->learnedInstantSpellList.emplace_front(result->getString("name"));
} while (result->next());
}
//load inventory items
ItemMap itemMap;
query.str(std::string());
query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC";
if ((result = db->storeQuery(query.str()))) {
loadItems(itemMap, result);
for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) {
const std::pair<Item*, int32_t>& pair = it->second;
Item* item = pair.first;
int32_t pid = pair.second;
if (pid >= 1 && pid <= 10) {
player->internalAddThing(pid, item);
} else {
ItemMap::const_iterator it2 = itemMap.find(pid);
if (it2 == itemMap.end()) {
continue;
}
Container* container = it2->second.first->getContainer();
if (container) {
container->internalAddThing(item);
}
}
}
}
//load depot items
itemMap.clear();
query.str(std::string());
query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC";
if ((result = db->storeQuery(query.str()))) {
loadItems(itemMap, result);
for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) {
const std::pair<Item*, int32_t>& pair = it->second;
Item* item = pair.first;
int32_t pid = pair.second;
if (pid >= 0 && pid < 100) {
Container* itemContainer = item->getContainer();
if (itemContainer) {
DepotLocker* locker = itemContainer->getDepotLocker();
if (locker) {
DepotLocker* existingLocker = player->getDepotLocker(pid, false);
if (!existingLocker) {
player->depotLockerMap[pid] = locker;
}
}
}
} else {
ItemMap::const_iterator it2 = itemMap.find(pid);
if (it2 == itemMap.end()) {
continue;
}
Container* container = it2->second.first->getContainer();
if (container) {
container->internalAddThing(item);
}
}
}
}
//load storage map
query.str(std::string());
query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID();
if ((result = db->storeQuery(query.str()))) {
do {
player->addStorageValue(result->getNumber<uint32_t>("key"), result->getNumber<int32_t>("value"));
} while (result->next());
}
//load vip
query.str(std::string());
query << "SELECT `player_id` FROM `account_viplist` WHERE `account_id` = " << player->getAccount();
if ((result = db->storeQuery(query.str()))) {
do {
player->addVIPInternal(result->getNumber<uint32_t>("player_id"));
} while (result->next());
}
player->updateBaseSpeed();
player->updateInventoryWeight();
player->updateItemsLight(true);
return true;
}
bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream)
{
std::ostringstream ss;
typedef std::pair<Container*, int32_t> containerBlock;
std::list<containerBlock> queue;
int32_t runningId = 100;
Database* db = Database::getInstance();
for (const auto& it : itemList) {
int32_t pid = it.first;
Item* item = it.second;
++runningId;
propWriteStream.clear();
item->serializeAttr(propWriteStream);
size_t attributesSize;
const char* attributes = propWriteStream.getStream(attributesSize);
ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize);
if (!query_insert.addRow(ss)) {
return false;
}
if (Container* container = item->getContainer()) {
queue.emplace_back(container, runningId);
}
}
while (!queue.empty()) {
const containerBlock& cb = queue.front();
Container* container = cb.first;
int32_t parentId = cb.second;
queue.pop_front();
for (Item* item : container->getItemList()) {
++runningId;
Container* subContainer = item->getContainer();
if (subContainer) {
queue.emplace_back(subContainer, runningId);
}
propWriteStream.clear();
item->serializeAttr(propWriteStream);
size_t attributesSize;
const char* attributes = propWriteStream.getStream(attributesSize);
ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize);
if (!query_insert.addRow(ss)) {
return false;
}
}
}
return query_insert.execute();
}
bool IOLoginData::savePlayer(Player* player)
{
if (player->getHealth() <= 0) {
player->changeHealth(1);
}
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `save` FROM `players` WHERE `id` = " << player->getGUID();
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return false;
}
if (result->getNumber<uint16_t>("save") == 0) {
query.str(std::string());
query << "UPDATE `players` SET `lastlogin` = " << player->lastLoginSaved << ", `lastip` = " << player->lastIP << " WHERE `id` = " << player->getGUID();
return db->executeQuery(query.str());
}
//serialize conditions
PropWriteStream propWriteStream;
for (Condition* condition : player->conditions) {
if (condition->isPersistent()) {
condition->serialize(propWriteStream);
propWriteStream.write<uint8_t>(CONDITIONATTR_END);
}
}
size_t conditionsSize;
const char* conditions = propWriteStream.getStream(conditionsSize);
//First, an UPDATE query to write the player itself
query.str(std::string());
query << "UPDATE `players` SET ";
query << "`level` = " << player->level << ',';
query << "`group_id` = " << player->group->id << ',';
query << "`vocation` = " << player->getVocationId() << ',';
query << "`health` = " << player->health << ',';
query << "`healthmax` = " << player->healthMax << ',';
query << "`experience` = " << player->experience << ',';
query << "`lookbody` = " << static_cast<uint32_t>(player->defaultOutfit.lookBody) << ',';
query << "`lookfeet` = " << static_cast<uint32_t>(player->defaultOutfit.lookFeet) << ',';
query << "`lookhead` = " << static_cast<uint32_t>(player->defaultOutfit.lookHead) << ',';
query << "`looklegs` = " << static_cast<uint32_t>(player->defaultOutfit.lookLegs) << ',';
query << "`looktype` = " << player->defaultOutfit.lookType << ',';
query << "`maglevel` = " << player->magLevel << ',';
query << "`mana` = " << player->mana << ',';
query << "`manamax` = " << player->manaMax << ',';
query << "`manaspent` = " << player->manaSpent << ',';
query << "`soul` = " << static_cast<uint16_t>(player->soul) << ',';
query << "`town_id` = " << player->town->getID() << ',';
const Position& loginPosition = player->getLoginPosition();
query << "`posx` = " << loginPosition.getX() << ',';
query << "`posy` = " << loginPosition.getY() << ',';
query << "`posz` = " << loginPosition.getZ() << ',';
query << "`cap` = " << (player->capacity / 100) << ',';
query << "`sex` = " << player->sex << ',';
if (player->lastLoginSaved != 0) {
query << "`lastlogin` = " << player->lastLoginSaved << ',';
}
if (player->lastIP != 0) {
query << "`lastip` = " << player->lastIP << ',';
}
query << "`conditions` = " << db->escapeBlob(conditions, conditionsSize) << ',';
if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) {
query << "`skulltime` = " << player->getPlayerKillerEnd() << ',';
Skulls_t skull = SKULL_NONE;
if (player->skull == SKULL_RED) {
skull = SKULL_RED;
}
query << "`skull` = " << static_cast<uint32_t>(skull) << ',';
}
query << "`lastlogout` = " << player->getLastLogout() << ',';
query << "`balance` = " << player->bankBalance << ',';
query << "`skill_fist` = " << player->skills[SKILL_FIST].level << ',';
query << "`skill_fist_tries` = " << player->skills[SKILL_FIST].tries << ',';
query << "`skill_club` = " << player->skills[SKILL_CLUB].level << ',';
query << "`skill_club_tries` = " << player->skills[SKILL_CLUB].tries << ',';
query << "`skill_sword` = " << player->skills[SKILL_SWORD].level << ',';
query << "`skill_sword_tries` = " << player->skills[SKILL_SWORD].tries << ',';
query << "`skill_axe` = " << player->skills[SKILL_AXE].level << ',';
query << "`skill_axe_tries` = " << player->skills[SKILL_AXE].tries << ',';
query << "`skill_dist` = " << player->skills[SKILL_DISTANCE].level << ',';
query << "`skill_dist_tries` = " << player->skills[SKILL_DISTANCE].tries << ',';
query << "`skill_shielding` = " << player->skills[SKILL_SHIELD].level << ',';
query << "`skill_shielding_tries` = " << player->skills[SKILL_SHIELD].tries << ',';
query << "`skill_fishing` = " << player->skills[SKILL_FISHING].level << ',';
query << "`skill_fishing_tries` = " << player->skills[SKILL_FISHING].tries << ',';
if (!player->isOffline()) {
query << "`onlinetime` = `onlinetime` + " << (time(nullptr) - player->lastLoginSaved) << ',';
}
query << "`blessings` = " << static_cast<uint32_t>(player->blessings);
query << " WHERE `id` = " << player->getGUID();
DBTransaction transaction;
if (!transaction.begin()) {
return false;
}
if (!db->executeQuery(query.str())) {
return false;
}
// learned spells
query.str(std::string());
query << "DELETE FROM `player_spells` WHERE `player_id` = " << player->getGUID();
if (!db->executeQuery(query.str())) {
return false;
}
query.str(std::string());
DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name` ) VALUES ");
for (const std::string& spellName : player->learnedInstantSpellList) {
query << player->getGUID() << ',' << db->escapeString(spellName);
if (!spellsQuery.addRow(query)) {
return false;
}
}
if (!spellsQuery.execute()) {
return false;
}
query.str(std::string());
query << "DELETE FROM `player_murders` WHERE `player_id` = " << player->getGUID();
if (!db->executeQuery(query.str())) {
return false;
}
query.str(std::string());
DBInsert murdersQuery("INSERT INTO `player_murders`(`id`, `player_id`, `date`) VALUES ");
for (time_t timestamp : player->murderTimeStamps) {
query << "NULL," << player->getGUID() << ',' << timestamp;
if (!murdersQuery.addRow(query)) {
return false;
}
}
if (!murdersQuery.execute()) {
return false;
}
//item saving
query.str(std::string());
query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID();
if (!db->executeQuery(query.str())) {
return false;
}
DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ");
ItemBlockList itemList;
for (int32_t slotId = 1; slotId <= 10; ++slotId) {
Item* item = player->inventory[slotId];
if (item) {
itemList.emplace_back(slotId, item);
}
}
if (!saveItems(player, itemList, itemsQuery, propWriteStream)) {
return false;
}
//save depot items
query.str(std::string());
query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID();
if (!db->executeQuery(query.str())) {
return false;
}
DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ");
itemList.clear();
for (const auto& it : player->depotLockerMap) {
itemList.emplace_back(it.first, it.second);
}
if (!saveItems(player, itemList, depotQuery, propWriteStream)) {
return false;
}
query.str(std::string());
query << "DELETE FROM `player_storage` WHERE `player_id` = " << player->getGUID();
if (!db->executeQuery(query.str())) {
return false;
}
query.str(std::string());
DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES ");
for (const auto& it : player->storageMap) {
query << player->getGUID() << ',' << it.first << ',' << it.second;
if (!storageQuery.addRow(query)) {
return false;
}
}
if (!storageQuery.execute()) {
return false;
}
//End the transaction
return transaction.commit();
}
std::string IOLoginData::getNameByGuid(uint32_t guid)
{
std::ostringstream query;
query << "SELECT `name` FROM `players` WHERE `id` = " << guid;
DBResult_ptr result = Database::getInstance()->storeQuery(query.str());
if (!result) {
return std::string();
}
return result->getString("name");
}
uint32_t IOLoginData::getGuidByName(const std::string& name)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `id` FROM `players` WHERE `name` = " << db->escapeString(name);
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return 0;
}
return result->getNumber<uint32_t>("id");
}
bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `name`, `id`, `group_id`, `account_id` FROM `players` WHERE `name` = " << db->escapeString(name);
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return false;
}
name = result->getString("name");
guid = result->getNumber<uint32_t>("id");
Group* group = g_game.groups.getGroup(result->getNumber<uint16_t>("group_id"));
uint64_t flags;
if (group) {
flags = group->flags;
} else {
flags = 0;
}
specialVip = (flags & PlayerFlag_SpecialVIP) != 0;
return true;
}
bool IOLoginData::formatPlayerName(std::string& name)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `name` FROM `players` WHERE `name` = " << db->escapeString(name);
DBResult_ptr result = db->storeQuery(query.str());
if (!result) {
return false;
}
name = result->getString("name");
return true;
}
void IOLoginData::loadItems(ItemMap& itemMap, DBResult_ptr result)
{
do {
uint32_t sid = result->getNumber<uint32_t>("sid");
uint32_t pid = result->getNumber<uint32_t>("pid");
uint16_t type = result->getNumber<uint16_t>("itemtype");
uint16_t count = result->getNumber<uint16_t>("count");
unsigned long attrSize;
const char* attr = result->getStream("attributes", attrSize);
PropStream propStream;
propStream.init(attr, attrSize);
Item* item = Item::CreateItem(type, count);
if (item) {
if (!item->unserializeAttr(propStream)) {
std::cout << "WARNING: Serialize error in IOLoginData::loadItems" << std::endl;
}
std::pair<Item*, uint32_t> pair(item, pid);
itemMap[sid] = pair;
}
} while (result->next());
}
void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance)
{
std::ostringstream query;
query << "UPDATE `players` SET `balance` = `balance` + " << bankBalance << " WHERE `id` = " << guid;
Database::getInstance()->executeQuery(query.str());
}
bool IOLoginData::hasBiddedOnHouse(uint32_t guid)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "SELECT `id` FROM `houses` WHERE `highest_bidder` = " << guid << " LIMIT 1";
return db->storeQuery(query.str()).get() != nullptr;
}
std::forward_list<VIPEntry> IOLoginData::getVIPEntries(uint32_t accountId)
{
std::forward_list<VIPEntry> entries;
std::ostringstream query;
query << "SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name` FROM `account_viplist` WHERE `account_id` = " << accountId;
DBResult_ptr result = Database::getInstance()->storeQuery(query.str());
if (result) {
do {
entries.emplace_front(
result->getNumber<uint32_t>("player_id"),
result->getString("name")
);
} while (result->next());
}
return entries;
}
void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid)
{
Database* db = Database::getInstance();
std::ostringstream query;
query << "INSERT INTO `account_viplist` (`account_id`, `player_id`) VALUES (" << accountId << ',' << guid << ')';
db->executeQuery(query.str());
}
void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid)
{
std::ostringstream query;
query << "DELETE FROM `account_viplist` WHERE `account_id` = " << accountId << " AND `player_id` = " << guid;
Database::getInstance()->executeQuery(query.str());
}
void IOLoginData::addPremiumDays(uint32_t accountId, int32_t addDays)
{
std::ostringstream query;
query << "UPDATE `accounts` SET `premdays` = `premdays` + " << addDays << " WHERE `id` = " << accountId;
Database::getInstance()->executeQuery(query.str());
}
void IOLoginData::removePremiumDays(uint32_t accountId, int32_t removeDays)
{
std::ostringstream query;
query << "UPDATE `accounts` SET `premdays` = `premdays` - " << removeDays << " WHERE `id` = " << accountId;
Database::getInstance()->executeQuery(query.str());
}

68
src/iologindata.h Normal file
View File

@@ -0,0 +1,68 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF
#define FS_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF
#include "account.h"
#include "player.h"
#include "database.h"
typedef std::list<std::pair<int32_t, Item*>> ItemBlockList;
class IOLoginData
{
public:
static Account loadAccount(uint32_t accno);
static bool saveAccount(const Account& acc);
static bool loginserverAuthentication(uint32_t accountNumber, const std::string& password, Account& account);
static uint32_t gameworldAuthentication(uint32_t accountNumber, const std::string& password, std::string& characterName);
static AccountType_t getAccountType(uint32_t accountId);
static void setAccountType(uint32_t accountId, AccountType_t accountType);
static void updateOnlineStatus(uint32_t guid, bool login);
static bool preloadPlayer(Player* player, const std::string& name);
static bool loadPlayerById(Player* player, uint32_t id);
static bool loadPlayerByName(Player* player, const std::string& name);
static bool loadPlayer(Player* player, DBResult_ptr result);
static bool savePlayer(Player* player);
static uint32_t getGuidByName(const std::string& name);
static bool getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name);
static std::string getNameByGuid(uint32_t guid);
static bool formatPlayerName(std::string& name);
static void increaseBankBalance(uint32_t guid, uint64_t bankBalance);
static bool hasBiddedOnHouse(uint32_t guid);
static std::forward_list<VIPEntry> getVIPEntries(uint32_t accountId);
static void addVIPEntry(uint32_t accountId, uint32_t guid);
static void removeVIPEntry(uint32_t accountId, uint32_t guid);
static void addPremiumDays(uint32_t accountId, int32_t addDays);
static void removePremiumDays(uint32_t accountId, int32_t removeDays);
protected:
typedef std::map<uint32_t, std::pair<Item*, uint32_t>> ItemMap;
static void loadItems(ItemMap& itemMap, DBResult_ptr result);
static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& stream);
};
#endif

462
src/iomap.cpp Normal file
View File

@@ -0,0 +1,462 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "iomap.h"
#include "bed.h"
/*
OTBM_ROOTV1
|
|--- OTBM_MAP_DATA
| |
| |--- OTBM_TILE_AREA
| | |--- OTBM_TILE
| | |--- OTBM_TILE_SQUARE (not implemented)
| | |--- OTBM_TILE_REF (not implemented)
| | |--- OTBM_HOUSETILE
| |
| |--- OTBM_SPAWNS (not implemented)
| | |--- OTBM_SPAWN_AREA (not implemented)
| | |--- OTBM_MONSTER (not implemented)
| |
| |--- OTBM_TOWNS
| | |--- OTBM_TOWN
| |
| |--- OTBM_WAYPOINTS
| |--- OTBM_WAYPOINT
|
|--- OTBM_ITEM_DEF (not implemented)
*/
Tile* IOMap::createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z)
{
if (!ground) {
return new StaticTile(x, y, z);
}
Tile* tile;
if ((item && item->isBlocking()) || ground->isBlocking()) {
tile = new StaticTile(x, y, z);
} else {
tile = new DynamicTile(x, y, z);
}
tile->internalAddThing(ground);
ground->startDecaying();
ground = nullptr;
return tile;
}
bool IOMap::loadMap(Map* map, const std::string& identifier)
{
int64_t start = OTSYS_TIME();
FileLoader f;
if (!f.openFile(identifier.c_str(), "OTBM")) {
std::ostringstream ss;
ss << "Could not open the file " << identifier << '.';
setLastErrorString(ss.str());
return false;
}
uint32_t type;
PropStream propStream;
NODE root = f.getChildNode(nullptr, type);
if (!f.getProps(root, propStream)) {
setLastErrorString("Could not read root property.");
return false;
}
OTBM_root_header root_header;
if (!propStream.read(root_header)) {
setLastErrorString("Could not read header.");
return false;
}
uint32_t headerVersion = root_header.version;
if (headerVersion <= 0) {
//In otbm version 1 the count variable after splashes/fluidcontainers and stackables
//are saved as attributes instead, this solves alot of problems with items
//that is changed (stackable/charges/fluidcontainer/splash) during an update.
setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly.");
return false;
}
if (headerVersion > 2) {
setLastErrorString("Unknown OTBM version detected.");
return false;
}
std::cout << "> Map size: " << root_header.width << "x" << root_header.height << '.' << std::endl;
map->width = root_header.width;
map->height = root_header.height;
NODE nodeMap = f.getChildNode(root, type);
if (type != OTBM_MAP_DATA) {
setLastErrorString("Could not read data node.");
return false;
}
if (!f.getProps(nodeMap, propStream)) {
setLastErrorString("Could not read map data attributes.");
return false;
}
std::string mapDescription;
std::string tmp;
uint8_t attribute;
while (propStream.read<uint8_t>(attribute)) {
switch (attribute) {
case OTBM_ATTR_DESCRIPTION:
if (!propStream.readString(mapDescription)) {
setLastErrorString("Invalid description tag.");
return false;
}
break;
case OTBM_ATTR_EXT_SPAWN_FILE:
if (!propStream.readString(tmp)) {
setLastErrorString("Invalid spawn tag.");
return false;
}
map->spawnfile = identifier.substr(0, identifier.rfind('/') + 1);
map->spawnfile += tmp;
break;
case OTBM_ATTR_EXT_HOUSE_FILE:
if (!propStream.readString(tmp)) {
setLastErrorString("Invalid house tag.");
return false;
}
map->housefile = identifier.substr(0, identifier.rfind('/') + 1);
map->housefile += tmp;
break;
default:
setLastErrorString("Unknown header node.");
return false;
}
}
NODE nodeMapData = f.getChildNode(nodeMap, type);
while (nodeMapData != NO_NODE) {
if (f.getError() != ERROR_NONE) {
setLastErrorString("Invalid map node.");
return false;
}
if (type == OTBM_TILE_AREA) {
if (!f.getProps(nodeMapData, propStream)) {
setLastErrorString("Invalid map node.");
return false;
}
OTBM_Destination_coords area_coord;
if (!propStream.read(area_coord)) {
setLastErrorString("Invalid map node.");
return false;
}
uint16_t base_x = area_coord.x;
uint16_t base_y = area_coord.y;
uint16_t z = area_coord.z;
NODE nodeTile = f.getChildNode(nodeMapData, type);
while (nodeTile != NO_NODE) {
if (f.getError() != ERROR_NONE) {
setLastErrorString("Could not read node data.");
return false;
}
if (type != OTBM_TILE && type != OTBM_HOUSETILE) {
setLastErrorString("Unknown tile node.");
return false;
}
if (!f.getProps(nodeTile, propStream)) {
setLastErrorString("Could not read node data.");
return false;
}
OTBM_Tile_coords tile_coord;
if (!propStream.read(tile_coord)) {
setLastErrorString("Could not read tile position.");
return false;
}
uint16_t x = base_x + tile_coord.x;
uint16_t y = base_y + tile_coord.y;
bool isHouseTile = false;
House* house = nullptr;
Tile* tile = nullptr;
Item* ground_item = nullptr;
uint32_t tileflags = TILESTATE_NONE;
if (type == OTBM_HOUSETILE) {
uint32_t houseId;
if (!propStream.read<uint32_t>(houseId)) {
std::ostringstream ss;
ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id.";
setLastErrorString(ss.str());
return false;
}
house = map->houses.addHouse(houseId);
if (!house) {
std::ostringstream ss;
ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not create house id: " << houseId;
setLastErrorString(ss.str());
return false;
}
tile = new HouseTile(x, y, z, house);
house->addTile(static_cast<HouseTile*>(tile));
isHouseTile = true;
}
//read tile attributes
while (propStream.read<uint8_t>(attribute)) {
switch (attribute) {
case OTBM_ATTR_TILE_FLAGS: {
uint32_t flags;
if (!propStream.read<uint32_t>(flags)) {
std::ostringstream ss;
ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to read tile flags.";
setLastErrorString(ss.str());
return false;
}
if ((flags & OTBM_TILEFLAG_PROTECTIONZONE) != 0) {
tileflags |= TILESTATE_PROTECTIONZONE;
} else if ((flags & OTBM_TILEFLAG_NOPVPZONE) != 0) {
tileflags |= TILESTATE_NOPVPZONE;
} else if ((flags & OTBM_TILEFLAG_PVPZONE) != 0) {
tileflags |= TILESTATE_PVPZONE;
}
if ((flags & OTBM_TILEFLAG_REFRESH) != 0) {
tileflags |= TILESTATE_REFRESH;
}
if ((flags & OTBM_TILEFLAG_NOLOGOUT) != 0) {
tileflags |= TILESTATE_NOLOGOUT;
}
break;
}
case OTBM_ATTR_ITEM: {
Item* item = Item::CreateItem(propStream);
if (!item) {
std::ostringstream ss;
ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item.";
setLastErrorString(ss.str());
return false;
}
if (isHouseTile && item->isMoveable()) {
//std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl;
delete item;
} else {
if (item->getItemCount() <= 0) {
item->setItemCount(1);
}
if (tile) {
tile->internalAddThing(item);
item->startDecaying();
item->setLoadedFromMap(true);
} else if (item->isGroundTile()) {
delete ground_item;
ground_item = item;
} else {
tile = createTile(ground_item, item, x, y, z);
tile->internalAddThing(item);
item->startDecaying();
item->setLoadedFromMap(true);
}
}
break;
}
default:
std::ostringstream ss;
ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown tile attribute.";
setLastErrorString(ss.str());
return false;
}
}
NODE nodeItem = f.getChildNode(nodeTile, type);
while (nodeItem) {
if (type != OTBM_ITEM) {
std::ostringstream ss;
ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown node type.";
setLastErrorString(ss.str());
return false;
}
PropStream stream;
if (!f.getProps(nodeItem, stream)) {
setLastErrorString("Invalid item node.");
return false;
}
Item* item = Item::CreateItem(stream);
if (!item) {
std::ostringstream ss;
ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item.";
setLastErrorString(ss.str());
return false;
}
if (!item->unserializeItemNode(f, nodeItem, stream)) {
std::ostringstream ss;
ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to load item " << item->getID() << '.';
setLastErrorString(ss.str());
delete item;
return false;
}
if (isHouseTile && item->isMoveable()) {
//std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl;
delete item;
} else {
if (item->getItemCount() <= 0) {
item->setItemCount(1);
}
if (tile) {
tile->internalAddThing(item);
item->startDecaying();
item->setLoadedFromMap(true);
} else if (item->isGroundTile()) {
delete ground_item;
ground_item = item;
} else {
tile = createTile(ground_item, item, x, y, z);
tile->internalAddThing(item);
item->startDecaying();
item->setLoadedFromMap(true);
}
}
nodeItem = f.getNextNode(nodeItem, type);
}
if (!tile) {
tile = createTile(ground_item, nullptr, x, y, z);
}
tile->setFlag(static_cast<tileflags_t>(tileflags));
map->setTile(x, y, z, tile);
nodeTile = f.getNextNode(nodeTile, type);
}
} else if (type == OTBM_TOWNS) {
NODE nodeTown = f.getChildNode(nodeMapData, type);
while (nodeTown != NO_NODE) {
if (type != OTBM_TOWN) {
setLastErrorString("Unknown town node.");
return false;
}
if (!f.getProps(nodeTown, propStream)) {
setLastErrorString("Could not read town data.");
return false;
}
uint32_t townId;
if (!propStream.read<uint32_t>(townId)) {
setLastErrorString("Could not read town id.");
return false;
}
Town* town = map->towns.getTown(townId);
if (!town) {
town = new Town(townId);
map->towns.addTown(townId, town);
}
std::string townName;
if (!propStream.readString(townName)) {
setLastErrorString("Could not read town name.");
return false;
}
town->setName(townName);
OTBM_Destination_coords town_coords;
if (!propStream.read(town_coords)) {
setLastErrorString("Could not read town coordinates.");
return false;
}
town->setTemplePos(Position(town_coords.x, town_coords.y, town_coords.z));
nodeTown = f.getNextNode(nodeTown, type);
}
} else if (type == OTBM_WAYPOINTS) {
NODE nodeWaypoint = f.getChildNode(nodeMapData, type);
while (nodeWaypoint != NO_NODE) {
if (type != OTBM_WAYPOINT) {
setLastErrorString("Unknown waypoint node.");
return false;
}
if (!f.getProps(nodeWaypoint, propStream)) {
setLastErrorString("Could not read waypoint data.");
return false;
}
std::string name;
if (!propStream.readString(name)) {
setLastErrorString("Could not read waypoint name.");
return false;
}
OTBM_Destination_coords waypoint_coords;
if (!propStream.read(waypoint_coords)) {
setLastErrorString("Could not read waypoint coordinates.");
return false;
}
map->waypoints[name] = Position(waypoint_coords.x, waypoint_coords.y, waypoint_coords.z);
nodeWaypoint = f.getNextNode(nodeWaypoint, type);
}
} else {
setLastErrorString("Unknown map node.");
return false;
}
nodeMapData = f.getNextNode(nodeMapData, type);
}
std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl;
return true;
}

161
src/iomap.h Normal file
View File

@@ -0,0 +1,161 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_IOMAP_H_8085D4B1037A44288494A52FDBB775E4
#define FS_IOMAP_H_8085D4B1037A44288494A52FDBB775E4
#include "item.h"
#include "map.h"
#include "house.h"
#include "spawn.h"
#include "configmanager.h"
extern ConfigManager g_config;
enum OTBM_AttrTypes_t {
OTBM_ATTR_DESCRIPTION = 1,
OTBM_ATTR_EXT_FILE = 2,
OTBM_ATTR_TILE_FLAGS = 3,
OTBM_ATTR_ACTION_ID = 4,
OTBM_ATTR_MOVEMENT_ID = 5,
OTBM_ATTR_TEXT = 6,
OTBM_ATTR_DESC = 7,
OTBM_ATTR_TELE_DEST = 8,
OTBM_ATTR_ITEM = 9,
OTBM_ATTR_DEPOT_ID = 10,
OTBM_ATTR_EXT_SPAWN_FILE = 11,
OTBM_ATTR_RUNE_CHARGES = 12,
OTBM_ATTR_EXT_HOUSE_FILE = 13,
OTBM_ATTR_HOUSEDOORID = 14,
OTBM_ATTR_COUNT = 15,
OTBM_ATTR_DURATION = 16,
OTBM_ATTR_DECAYING_STATE = 17,
OTBM_ATTR_WRITTENDATE = 18,
OTBM_ATTR_WRITTENBY = 19,
OTBM_ATTR_SLEEPERGUID = 20,
OTBM_ATTR_SLEEPSTART = 21,
OTBM_ATTR_CHARGES = 22,
OTBM_ATTR_KEYNUMBER = 23,
OTBM_ATTR_KEYHOLENUMBER = 24,
OTBM_ATTR_DOORQUESTNUMBER = 25,
OTBM_ATTR_DOORQUESTVALUE = 26,
OTBM_ATTR_DOORLEVEL = 27,
OTBM_ATTR_CHESTQUESTNUMBER = 28,
};
enum OTBM_NodeTypes_t {
OTBM_ROOTV1 = 1,
OTBM_MAP_DATA = 2,
OTBM_ITEM_DEF = 3,
OTBM_TILE_AREA = 4,
OTBM_TILE = 5,
OTBM_ITEM = 6,
OTBM_TILE_SQUARE = 7,
OTBM_TILE_REF = 8,
OTBM_SPAWNS = 9,
OTBM_SPAWN_AREA = 10,
OTBM_MONSTER = 11,
OTBM_TOWNS = 12,
OTBM_TOWN = 13,
OTBM_HOUSETILE = 14,
OTBM_WAYPOINTS = 15,
OTBM_WAYPOINT = 16,
};
enum OTBM_TileFlag_t : uint32_t {
OTBM_TILEFLAG_PROTECTIONZONE = 1 << 0,
OTBM_TILEFLAG_NOPVPZONE = 1 << 2,
OTBM_TILEFLAG_NOLOGOUT = 1 << 3,
OTBM_TILEFLAG_PVPZONE = 1 << 4,
OTBM_TILEFLAG_REFRESH = 1 << 5,
};
#pragma pack(1)
struct OTBM_root_header {
uint32_t version;
uint16_t width;
uint16_t height;
uint32_t majorVersionItems;
uint32_t minorVersionItems;
};
struct OTBM_Destination_coords {
uint16_t x;
uint16_t y;
uint8_t z;
};
struct OTBM_Tile_coords {
uint8_t x;
uint8_t y;
};
#pragma pack()
class IOMap
{
static Tile* createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z);
public:
bool loadMap(Map* map, const std::string& identifier);
/* Load the spawns
* \param map pointer to the Map class
* \returns Returns true if the spawns were loaded successfully
*/
static bool loadSpawns(Map* map) {
if (map->spawnfile.empty()) {
//OTBM file doesn't tell us about the spawnfile,
//lets guess it is mapname-spawn.xml.
map->spawnfile = g_config.getString(ConfigManager::MAP_NAME);
map->spawnfile += "-spawn.xml";
}
return map->spawns.loadFromXml(map->spawnfile);
}
/* Load the houses (not house tile-data)
* \param map pointer to the Map class
* \returns Returns true if the houses were loaded successfully
*/
static bool loadHouses(Map* map) {
if (map->housefile.empty()) {
//OTBM file doesn't tell us about the housefile,
//lets guess it is mapname-house.xml.
map->housefile = g_config.getString(ConfigManager::MAP_NAME);
map->housefile += "-house.xml";
}
return map->houses.loadHousesXML(map->housefile);
}
const std::string& getLastErrorString() const {
return errorString;
}
void setLastErrorString(std::string error) {
errorString = error;
}
protected:
std::string errorString;
};
#endif

372
src/iomapserialize.cpp Normal file
View File

@@ -0,0 +1,372 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "iomapserialize.h"
#include "game.h"
#include "bed.h"
extern Game g_game;
void IOMapSerialize::loadHouseItems(Map* map)
{
int64_t start = OTSYS_TIME();
DBResult_ptr result = Database::getInstance()->storeQuery("SELECT `data` FROM `tile_store`");
if (!result) {
return;
}
do {
unsigned long attrSize;
const char* attr = result->getStream("data", attrSize);
PropStream propStream;
propStream.init(attr, attrSize);
uint16_t x, y;
uint8_t z;
if (!propStream.read<uint16_t>(x) || !propStream.read<uint16_t>(y) || !propStream.read<uint8_t>(z)) {
continue;
}
Tile* tile = map->getTile(x, y, z);
if (!tile) {
continue;
}
uint32_t item_count;
if (!propStream.read<uint32_t>(item_count)) {
continue;
}
while (item_count--) {
loadItem(propStream, tile);
}
} while (result->next());
std::cout << "> Loaded house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" << std::endl;
}
bool IOMapSerialize::saveHouseItems()
{
int64_t start = OTSYS_TIME();
Database* db = Database::getInstance();
std::ostringstream query;
//Start the transaction
DBTransaction transaction;
if (!transaction.begin()) {
return false;
}
//clear old tile data
if (!db->executeQuery("DELETE FROM `tile_store`")) {
return false;
}
DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`) VALUES ");
PropWriteStream stream;
for (const auto& it : g_game.map.houses.getHouses()) {
//save house items
House* house = it.second;
for (HouseTile* tile : house->getTiles()) {
saveTile(stream, tile);
size_t attributesSize;
const char* attributes = stream.getStream(attributesSize);
if (attributesSize > 0) {
query << house->getId() << ',' << db->escapeBlob(attributes, attributesSize);
if (!stmt.addRow(query)) {
return false;
}
stream.clear();
}
}
}
if (!stmt.execute()) {
return false;
}
//End the transaction
bool success = transaction.commit();
std::cout << "> Saved house items in: " <<
(OTSYS_TIME() - start) / (1000.) << " s" << std::endl;
return success;
}
bool IOMapSerialize::loadContainer(PropStream& propStream, Container* container)
{
while (container->serializationCount > 0) {
if (!loadItem(propStream, container)) {
std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl;
return false;
}
container->serializationCount--;
}
uint8_t endAttr;
if (!propStream.read<uint8_t>(endAttr) || endAttr != 0) {
std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl;
return false;
}
return true;
}
bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent)
{
uint16_t id;
if (!propStream.read<uint16_t>(id)) {
return false;
}
Tile* tile = nullptr;
if (parent->getParent() == nullptr) {
tile = parent->getTile();
}
const ItemType& iType = Item::items[id];
if (iType.moveable || !tile) {
//create a new item
Item* item = Item::CreateItem(id);
if (item) {
if (item->unserializeAttr(propStream)) {
Container* container = item->getContainer();
if (container && !loadContainer(propStream, container)) {
delete item;
return false;
}
parent->internalAddThing(item);
item->startDecaying();
} else {
std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
delete item;
return false;
}
}
} else {
// Stationary items like doors/beds/blackboards/bookcases
Item* item = nullptr;
if (const TileItemVector* items = tile->getItemList()) {
for (Item* findItem : *items) {
if (findItem->getID() == id) {
item = findItem;
break;
} else if (iType.isDoor() && findItem->getDoor()) {
item = findItem;
break;
} else if (iType.isBed() && findItem->getBed()) {
item = findItem;
break;
}
}
}
if (item) {
if (item->unserializeAttr(propStream)) {
Container* container = item->getContainer();
if (container && !loadContainer(propStream, container)) {
return false;
}
g_game.transformItem(item, id);
} else {
std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
}
} else {
//The map changed since the last save, just read the attributes
std::unique_ptr<Item> dummy(Item::CreateItem(id));
if (dummy) {
dummy->unserializeAttr(propStream);
Container* container = dummy->getContainer();
if (container) {
if (!loadContainer(propStream, container)) {
return false;
}
} else if (BedItem* bedItem = dynamic_cast<BedItem*>(dummy.get())) {
uint32_t sleeperGUID = bedItem->getSleeper();
if (sleeperGUID != 0) {
g_game.removeBedSleeper(sleeperGUID);
}
}
}
}
}
return true;
}
void IOMapSerialize::saveItem(PropWriteStream& stream, const Item* item)
{
const Container* container = item->getContainer();
// Write ID & props
stream.write<uint16_t>(item->getID());
item->serializeAttr(stream);
if (container) {
// Hack our way into the attributes
stream.write<uint8_t>(ATTR_CONTAINER_ITEMS);
stream.write<uint32_t>(container->size());
for (auto it = container->getReversedItems(), end = container->getReversedEnd(); it != end; ++it) {
saveItem(stream, *it);
}
}
stream.write<uint8_t>(0x00); // attr end
}
void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile)
{
const TileItemVector* tileItems = tile->getItemList();
if (!tileItems) {
return;
}
std::forward_list<Item*> items;
uint16_t count = 0;
for (Item* item : *tileItems) {
const ItemType& it = Item::items[item->getID()];
// Note that these are NEGATED, ie. these are the items that will be saved.
if (!(it.moveable || item->getDoor() || (item->getContainer() && !item->getContainer()->empty()) || it.canWriteText || item->getBed())) {
continue;
}
items.push_front(item);
++count;
}
if (!items.empty()) {
const Position& tilePosition = tile->getPosition();
stream.write<uint16_t>(tilePosition.x);
stream.write<uint16_t>(tilePosition.y);
stream.write<uint8_t>(tilePosition.z);
stream.write<uint32_t>(count);
for (const Item* item : items) {
saveItem(stream, item);
}
}
}
bool IOMapSerialize::loadHouseInfo()
{
Database* db = Database::getInstance();
DBResult_ptr result = db->storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`");
if (!result) {
return false;
}
do {
House* house = g_game.map.houses.getHouse(result->getNumber<uint32_t>("id"));
if (house) {
house->setOwner(result->getNumber<uint32_t>("owner"), false);
house->setPaidUntil(result->getNumber<time_t>("paid"));
house->setPayRentWarnings(result->getNumber<uint32_t>("warnings"));
}
} while (result->next());
result = db->storeQuery("SELECT `house_id`, `listid`, `list` FROM `house_lists`");
if (result) {
do {
House* house = g_game.map.houses.getHouse(result->getNumber<uint32_t>("house_id"));
if (house) {
house->setAccessList(result->getNumber<uint32_t>("listid"), result->getString("list"));
}
} while (result->next());
}
return true;
}
bool IOMapSerialize::saveHouseInfo()
{
Database* db = Database::getInstance();
DBTransaction transaction;
if (!transaction.begin()) {
return false;
}
if (!db->executeQuery("DELETE FROM `house_lists`")) {
return false;
}
std::ostringstream query;
for (const auto& it : g_game.map.houses.getHouses()) {
House* house = it.second;
query << "SELECT `id` FROM `houses` WHERE `id` = " << house->getId();
DBResult_ptr result = db->storeQuery(query.str());
if (result) {
query.str(std::string());
query << "UPDATE `houses` SET `owner` = " << house->getOwner() << ", `paid` = " << house->getPaidUntil() << ", `warnings` = " << house->getPayRentWarnings() << ", `name` = " << db->escapeString(house->getName()) << ", `town_id` = " << house->getTownId() << ", `rent` = " << house->getRent() << ", `size` = " << house->getTiles().size() << ", `beds` = " << house->getBedCount() << " WHERE `id` = " << house->getId();
} else {
query.str(std::string());
query << "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES (" << house->getId() << ',' << house->getOwner() << ',' << house->getPaidUntil() << ',' << house->getPayRentWarnings() << ',' << db->escapeString(house->getName()) << ',' << house->getTownId() << ',' << house->getRent() << ',' << house->getTiles().size() << ',' << house->getBedCount() << ')';
}
db->executeQuery(query.str());
query.str(std::string());
}
DBInsert stmt("INSERT INTO `house_lists` (`house_id` , `listid` , `list`) VALUES ");
for (const auto& it : g_game.map.houses.getHouses()) {
House* house = it.second;
std::string listText;
if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) {
query << house->getId() << ',' << GUEST_LIST << ',' << db->escapeString(listText);
if (!stmt.addRow(query)) {
return false;
}
listText.clear();
}
if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) {
query << house->getId() << ',' << SUBOWNER_LIST << ',' << db->escapeString(listText);
if (!stmt.addRow(query)) {
return false;
}
listText.clear();
}
for (Door* door : house->getDoors()) {
if (door->getAccessList(listText) && !listText.empty()) {
query << house->getId() << ',' << door->getDoorId() << ',' << db->escapeString(listText);
if (!stmt.addRow(query)) {
return false;
}
listText.clear();
}
}
}
if (!stmt.execute()) {
return false;
}
return transaction.commit();
}

42
src/iomapserialize.h Normal file
View File

@@ -0,0 +1,42 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D
#define FS_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D
#include "database.h"
#include "map.h"
class IOMapSerialize
{
public:
static void loadHouseItems(Map* map);
static bool saveHouseItems();
static bool loadHouseInfo();
static bool saveHouseInfo();
protected:
static void saveItem(PropWriteStream& stream, const Item* item);
static void saveTile(PropWriteStream& stream, const Tile* tile);
static bool loadContainer(PropStream& propStream, Container* container);
static bool loadItem(PropStream& propStream, Cylinder* parent);
};
#endif

1242
src/item.cpp Normal file

File diff suppressed because it is too large Load Diff

835
src/item.h Normal file
View File

@@ -0,0 +1,835 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_ITEM_H_009A319FB13D477D9EEFFBBD9BB83562
#define FS_ITEM_H_009A319FB13D477D9EEFFBBD9BB83562
#include "cylinder.h"
#include "thing.h"
#include "items.h"
#include <deque>
class Creature;
class Player;
class Container;
class Depot;
class Teleport;
class Mailbox;
class Door;
class MagicField;
class BedItem;
enum ITEMPROPERTY {
CONST_PROP_BLOCKSOLID = 0,
CONST_PROP_HASHEIGHT,
CONST_PROP_BLOCKPROJECTILE,
CONST_PROP_BLOCKPATH,
CONST_PROP_ISVERTICAL,
CONST_PROP_ISHORIZONTAL,
CONST_PROP_MOVEABLE,
CONST_PROP_IMMOVABLEBLOCKSOLID,
CONST_PROP_IMMOVABLEBLOCKPATH,
CONST_PROP_IMMOVABLENOFIELDBLOCKPATH,
CONST_PROP_NOFIELDBLOCKPATH,
CONST_PROP_SUPPORTHANGABLE,
CONST_PROP_UNLAY,
};
enum TradeEvents_t {
ON_TRADE_TRANSFER,
ON_TRADE_CANCEL,
};
enum ItemDecayState_t : uint8_t {
DECAYING_FALSE = 0,
DECAYING_TRUE,
DECAYING_PENDING,
};
enum AttrTypes_t {
//ATTR_DESCRIPTION = 1,
//ATTR_EXT_FILE = 2,
ATTR_TILE_FLAGS = 3,
ATTR_ACTION_ID = 4,
ATTR_MOVEMENT_ID = 5,
ATTR_TEXT = 6,
ATTR_DESC = 7,
ATTR_TELE_DEST = 8,
ATTR_ITEM = 9,
ATTR_DEPOT_ID = 10,
//ATTR_EXT_SPAWN_FILE = 11,
ATTR_RUNE_CHARGES = 12,
//ATTR_EXT_HOUSE_FILE = 13,
ATTR_HOUSEDOORID = 14,
ATTR_COUNT = 15,
ATTR_DURATION = 16,
ATTR_DECAYING_STATE = 17,
ATTR_WRITTENDATE = 18,
ATTR_WRITTENBY = 19,
ATTR_SLEEPERGUID = 20,
ATTR_SLEEPSTART = 21,
ATTR_CHARGES = 22,
ATTR_KEYNUMBER = 23,
ATTR_KEYHOLENUMBER = 24,
ATTR_DOORQUESTNUMBER = 25,
ATTR_DOORQUESTVALUE = 26,
ATTR_DOORLEVEL = 27,
ATTR_CHESTQUESTNUMBER = 28,
// add non-OTBM attributes after here
ATTR_CONTAINER_ITEMS = 29,
ATTR_NAME = 30,
ATTR_ARTICLE = 31,
ATTR_PLURALNAME = 32,
ATTR_WEIGHT = 33,
ATTR_ATTACK = 34,
ATTR_DEFENSE = 35,
ATTR_ARMOR = 36,
ATTR_SHOOTRANGE = 37,
};
enum Attr_ReadValue {
ATTR_READ_CONTINUE,
ATTR_READ_ERROR,
ATTR_READ_END,
};
class ItemAttributes
{
public:
ItemAttributes() = default;
void setSpecialDescription(const std::string& desc) {
setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc);
}
const std::string& getSpecialDescription() const {
return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION);
}
void setText(const std::string& text) {
setStrAttr(ITEM_ATTRIBUTE_TEXT, text);
}
void resetText() {
removeAttribute(ITEM_ATTRIBUTE_TEXT);
}
const std::string& getText() const {
return getStrAttr(ITEM_ATTRIBUTE_TEXT);
}
void setDate(int32_t n) {
setIntAttr(ITEM_ATTRIBUTE_DATE, n);
}
void resetDate() {
removeAttribute(ITEM_ATTRIBUTE_DATE);
}
time_t getDate() const {
return static_cast<time_t>(getIntAttr(ITEM_ATTRIBUTE_DATE));
}
void setWriter(const std::string& writer) {
setStrAttr(ITEM_ATTRIBUTE_WRITER, writer);
}
void resetWriter() {
removeAttribute(ITEM_ATTRIBUTE_WRITER);
}
const std::string& getWriter() const {
return getStrAttr(ITEM_ATTRIBUTE_WRITER);
}
void setActionId(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n);
}
uint16_t getActionId() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_ACTIONID));
}
void setMovementID(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n);
}
uint16_t getMovementId() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID));
}
void setKeyNumber(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, n);
}
uint16_t getKeyNumber() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER));
}
void setKeyHoleNumber(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, n);
}
uint16_t getKeyHoleNumber() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER));
}
void setDoorQuestNumber(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, n);
}
uint16_t getDoorQuestNumber() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER));
}
void setDoorQuestValue(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, n);
}
uint16_t getDoorQuestValue() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE));
}
void setDoorLevel(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, n);
}
uint16_t getDoorLevel() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL));
}
void setChestQuestNumber(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, n);
}
uint16_t getChestQuestNumber() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER));
}
void setCharges(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_CHARGES, n);
}
uint16_t getCharges() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_CHARGES));
}
void setFluidType(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n);
}
uint16_t getFluidType() const {
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE));
}
void setOwner(uint32_t owner) {
setIntAttr(ITEM_ATTRIBUTE_OWNER, owner);
}
uint32_t getOwner() const {
return getIntAttr(ITEM_ATTRIBUTE_OWNER);
}
void setDuration(int32_t time) {
setIntAttr(ITEM_ATTRIBUTE_DURATION, time);
}
void decreaseDuration(int32_t time) {
increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time);
}
uint32_t getDuration() const {
return getIntAttr(ITEM_ATTRIBUTE_DURATION);
}
void setDecaying(ItemDecayState_t decayState) {
setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState);
}
ItemDecayState_t getDecaying() const {
return static_cast<ItemDecayState_t>(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE));
}
protected:
inline bool hasAttribute(itemAttrTypes type) const {
return (type & attributeBits) != 0;
}
void removeAttribute(itemAttrTypes type);
static std::string emptyString;
struct Attribute
{
union {
int64_t integer;
std::string* string;
} value;
itemAttrTypes type;
explicit Attribute(itemAttrTypes type) : type(type) {
memset(&value, 0, sizeof(value));
}
Attribute(const Attribute& i) {
type = i.type;
if (ItemAttributes::isIntAttrType(type)) {
value.integer = i.value.integer;
} else if (ItemAttributes::isStrAttrType(type)) {
value.string = new std::string(*i.value.string);
} else {
memset(&value, 0, sizeof(value));
}
}
Attribute(Attribute&& attribute) : value(attribute.value), type(attribute.type) {
memset(&attribute.value, 0, sizeof(value));
attribute.type = ITEM_ATTRIBUTE_NONE;
}
~Attribute() {
if (ItemAttributes::isStrAttrType(type)) {
delete value.string;
}
}
Attribute& operator=(Attribute other) {
Attribute::swap(*this, other);
return *this;
}
Attribute& operator=(Attribute&& other) {
if (this != &other) {
if (ItemAttributes::isStrAttrType(type)) {
delete value.string;
}
value = other.value;
type = other.type;
memset(&other.value, 0, sizeof(value));
other.type = ITEM_ATTRIBUTE_NONE;
}
return *this;
}
static void swap(Attribute& first, Attribute& second) {
std::swap(first.value, second.value);
std::swap(first.type, second.type);
}
};
std::forward_list<Attribute> attributes;
uint32_t attributeBits = 0;
const std::string& getStrAttr(itemAttrTypes type) const;
void setStrAttr(itemAttrTypes type, const std::string& value);
int64_t getIntAttr(itemAttrTypes type) const;
void setIntAttr(itemAttrTypes type, int64_t value);
void increaseIntAttr(itemAttrTypes type, int64_t value);
const Attribute* getExistingAttr(itemAttrTypes type) const;
Attribute& getAttr(itemAttrTypes type);
public:
inline static bool isIntAttrType(itemAttrTypes type) {
return (type & 0xFFFFE13) != 0;
}
inline static bool isStrAttrType(itemAttrTypes type) {
return (type & 0x1EC) != 0;
}
const std::forward_list<Attribute>& getList() const {
return attributes;
}
friend class Item;
};
class Item : virtual public Thing
{
public:
//Factory member to create item of right type based on type
static Item* CreateItem(const uint16_t type, uint16_t count = 0);
static Container* CreateItemAsContainer(const uint16_t type, uint16_t size);
static Item* CreateItem(PropStream& propStream);
static Items items;
// Constructor for items
Item(const uint16_t type, uint16_t count = 0);
Item(const Item& i);
virtual Item* clone() const;
virtual ~Item() = default;
// non-assignable
Item& operator=(const Item&) = delete;
bool equals(const Item* otherItem) const;
Item* getItem() final {
return this;
}
const Item* getItem() const final {
return this;
}
virtual Teleport* getTeleport() {
return nullptr;
}
virtual const Teleport* getTeleport() const {
return nullptr;
}
virtual Mailbox* getMailbox() {
return nullptr;
}
virtual const Mailbox* getMailbox() const {
return nullptr;
}
virtual Door* getDoor() {
return nullptr;
}
virtual const Door* getDoor() const {
return nullptr;
}
virtual MagicField* getMagicField() {
return nullptr;
}
virtual const MagicField* getMagicField() const {
return nullptr;
}
virtual BedItem* getBed() {
return nullptr;
}
virtual const BedItem* getBed() const {
return nullptr;
}
const std::string& getStrAttr(itemAttrTypes type) const {
if (!attributes) {
return ItemAttributes::emptyString;
}
return attributes->getStrAttr(type);
}
void setStrAttr(itemAttrTypes type, const std::string& value) {
getAttributes()->setStrAttr(type, value);
}
int32_t getIntAttr(itemAttrTypes type) const {
if (!attributes) {
return 0;
}
return attributes->getIntAttr(type);
}
void setIntAttr(itemAttrTypes type, int32_t value) {
getAttributes()->setIntAttr(type, value);
}
void increaseIntAttr(itemAttrTypes type, int32_t value) {
getAttributes()->increaseIntAttr(type, value);
}
void removeAttribute(itemAttrTypes type) {
if (attributes) {
attributes->removeAttribute(type);
}
}
bool hasAttribute(itemAttrTypes type) const {
if (!attributes) {
return false;
}
return attributes->hasAttribute(type);
}
void setSpecialDescription(const std::string& desc) {
setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc);
}
const std::string& getSpecialDescription() const {
return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION);
}
void setText(const std::string& text) {
setStrAttr(ITEM_ATTRIBUTE_TEXT, text);
}
void resetText() {
removeAttribute(ITEM_ATTRIBUTE_TEXT);
}
const std::string& getText() const {
return getStrAttr(ITEM_ATTRIBUTE_TEXT);
}
void setDate(int32_t n) {
setIntAttr(ITEM_ATTRIBUTE_DATE, n);
}
void resetDate() {
removeAttribute(ITEM_ATTRIBUTE_DATE);
}
time_t getDate() const {
return static_cast<time_t>(getIntAttr(ITEM_ATTRIBUTE_DATE));
}
void setWriter(const std::string& writer) {
setStrAttr(ITEM_ATTRIBUTE_WRITER, writer);
}
void resetWriter() {
removeAttribute(ITEM_ATTRIBUTE_WRITER);
}
const std::string& getWriter() const {
return getStrAttr(ITEM_ATTRIBUTE_WRITER);
}
void setActionId(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n);
}
uint16_t getActionId() const {
if (!attributes) {
return 0;
}
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_ACTIONID));
}
void setMovementID(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n);
}
uint16_t getMovementId() const {
if (!attributes) {
return 0;
}
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID));
}
void setCharges(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_CHARGES, n);
}
uint16_t getCharges() const {
if (!attributes) {
return 0;
}
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_CHARGES));
}
void setFluidType(uint16_t n) {
setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n);
}
uint16_t getFluidType() const {
if (!attributes) {
return 0;
}
return static_cast<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE));
}
void setOwner(uint32_t owner) {
setIntAttr(ITEM_ATTRIBUTE_OWNER, owner);
}
uint32_t getOwner() const {
if (!attributes) {
return 0;
}
return getIntAttr(ITEM_ATTRIBUTE_OWNER);
}
void setCorpseOwner(uint32_t corpseOwner) {
setIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER, corpseOwner);
}
uint32_t getCorpseOwner() const {
if (!attributes) {
return 0;
}
return getIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER);
}
void setDuration(int32_t time) {
setIntAttr(ITEM_ATTRIBUTE_DURATION, time);
}
void decreaseDuration(int32_t time) {
increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time);
}
uint32_t getDuration() const {
if (!attributes) {
return 0;
}
return getIntAttr(ITEM_ATTRIBUTE_DURATION);
}
void setDecaying(ItemDecayState_t decayState) {
setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState);
}
ItemDecayState_t getDecaying() const {
if (!attributes) {
return DECAYING_FALSE;
}
return static_cast<ItemDecayState_t>(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE));
}
static std::string getDescription(const ItemType& it, int32_t lookDistance, const Item* item = nullptr, int32_t subType = -1, bool addArticle = true);
static std::string getNameDescription(const ItemType& it, const Item* item = nullptr, int32_t subType = -1, bool addArticle = true);
static std::string getWeightDescription(const ItemType& it, uint32_t weight, uint32_t count = 1);
std::string getDescription(int32_t lookDistance) const final;
std::string getNameDescription() const;
std::string getWeightDescription() const;
//serialization
virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream);
bool unserializeAttr(PropStream& propStream);
virtual bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream);
virtual void serializeAttr(PropWriteStream& propWriteStream) const;
bool isPushable() const final {
return isMoveable();
}
int32_t getThrowRange() const final {
return (isPickupable() ? 15 : 2);
}
uint16_t getID() const {
return id;
}
void setID(uint16_t newid);
// Returns the player that is holding this item in his inventory
Player* getHoldingPlayer() const;
CombatType_t getDamageType() const {
return items[id].damageType;
}
CombatType_t getCombatType() const {
return items[id].combatType;
}
WeaponType_t getWeaponType() const {
return items[id].weaponType;
}
Ammo_t getAmmoType() const {
return items[id].ammoType;
}
uint8_t getShootRange() const {
if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) {
return getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE);
}
return items[id].shootRange;
}
uint8_t getMissileType() const {
return items[id].shootType;
}
uint8_t getFragility() const {
return items[id].fragility;
}
int32_t getAttackStrength() const {
return items[id].attackStrength;
}
int32_t getAttackVariation() const {
return items[id].attackVariation;
}
int32_t getManaConsumption() const {
return items[id].manaConsumption;
}
uint32_t getMinimumLevel() const {
return items[id].minReqLevel;
}
int32_t getWeaponSpecialEffect() const {
return items[id].weaponSpecialEffect;
}
virtual uint32_t getWeight() const;
uint32_t getBaseWeight() const {
if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) {
return getIntAttr(ITEM_ATTRIBUTE_WEIGHT);
}
return items[id].weight;
}
int32_t getAttack() const {
if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) {
return getIntAttr(ITEM_ATTRIBUTE_ATTACK);
}
return items[id].attack;
}
int32_t getArmor() const {
if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) {
return getIntAttr(ITEM_ATTRIBUTE_ARMOR);
}
return items[id].armor;
}
int32_t getDefense() const {
if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) {
return getIntAttr(ITEM_ATTRIBUTE_DEFENSE);
}
return items[id].defense;
}
int32_t getSlotPosition() const {
return items[id].slotPosition;
}
uint16_t getDisguiseId() const {
return items[id].disguiseId;
}
uint32_t getWorth() const;
void getLight(LightInfo& lightInfo) const;
bool hasProperty(ITEMPROPERTY prop) const;
bool isBlocking() const {
return items[id].blockSolid;
}
bool isStackable() const {
return items[id].stackable;
}
bool isAlwaysOnTop() const {
return items[id].alwaysOnTop;
}
bool isGroundTile() const {
return items[id].isGroundTile();
}
bool isMagicField() const {
return items[id].isMagicField();
}
bool isSplash() const {
return items[id].isSplash();
}
bool isMoveable() const {
return items[id].moveable;
}
bool isPickupable() const {
return items[id].pickupable;
}
bool isHangable() const {
return items[id].isHangable;
}
bool isRotatable() const {
const ItemType& it = items[id];
return it.rotatable && it.rotateTo;
}
bool isDisguised() const {
return items[id].disguise;
}
bool isChangeUse() const {
return items[id].changeUse;
}
bool isChestQuest() const {
return items[id].isChest();
}
bool hasCollisionEvent() const {
return items[id].collisionEvent;
}
bool hasSeparationEvent() const {
return items[id].separationEvent;
}
bool hasUseEvent() const {
return items[id].useEvent;
}
bool hasMultiUseEvent() const {
return items[id].multiUseEvent;
}
bool canDistUse() const {
return items[id].distUse;
}
const std::string& getName() const {
if (hasAttribute(ITEM_ATTRIBUTE_NAME)) {
return getStrAttr(ITEM_ATTRIBUTE_NAME);
}
return items[id].name;
}
const std::string getPluralName() const {
if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) {
return getStrAttr(ITEM_ATTRIBUTE_PLURALNAME);
}
return items[id].getPluralName();
}
const std::string& getArticle() const {
if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) {
return getStrAttr(ITEM_ATTRIBUTE_ARTICLE);
}
return items[id].article;
}
// get the number of items
uint16_t getItemCount() const {
return count;
}
void setItemCount(uint8_t n) {
count = n;
}
static uint32_t countByType(const Item* i, int32_t subType);
void setDefaultSubtype();
uint16_t getSubType() const;
void setSubType(uint16_t n);
void setDefaultDuration() {
uint32_t duration = getDefaultDuration();
if (duration != 0) {
setDuration(duration);
}
}
uint32_t getDefaultDuration() const {
return items[id].decayTime * 1000;
}
bool canDecay() const;
virtual bool canRemove() const {
return true;
}
virtual bool canTransform() const {
return true;
}
virtual void onRemoved();
virtual void onTradeEvent(TradeEvents_t, Player*) {}
virtual void startDecaying();
void setLoadedFromMap(bool value) {
loadedFromMap = value;
}
bool isCleanable() const {
return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ITEM_ATTRIBUTE_ACTIONID);
}
std::unique_ptr<ItemAttributes>& getAttributes() {
if (!attributes) {
attributes.reset(new ItemAttributes());
}
return attributes;
}
void incrementReferenceCounter() {
++referenceCounter;
}
void decrementReferenceCounter() {
if (--referenceCounter == 0) {
delete this;
}
}
Cylinder* getParent() const {
return parent;
}
void setParent(Cylinder* cylinder) {
parent = cylinder;
}
Cylinder* getTopParent();
const Cylinder* getTopParent() const;
Tile* getTile();
const Tile* getTile() const;
bool isRemoved() const {
return !parent || parent->isRemoved();
}
protected:
std::string getWeightDescription(uint32_t weight) const;
Cylinder* parent = nullptr;
std::unique_ptr<ItemAttributes> attributes;
uint32_t referenceCounter = 0;
uint16_t id; // the same id as in ItemType
uint8_t count = 1; // number of stacked items
bool loadedFromMap = false;
//Don't add variables here, use the ItemAttribute class.
};
typedef std::list<Item*> ItemList;
typedef std::deque<Item*> ItemDeque;
inline uint32_t Item::countByType(const Item* i, int32_t subType)
{
if (subType == -1 || subType == i->getSubType()) {
return i->getItemCount();
}
return 0;
}
#endif

606
src/items.cpp Normal file
View File

@@ -0,0 +1,606 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "items.h"
#include "spells.h"
#include "movement.h"
#include "script.h"
#include "pugicast.h"
extern MoveEvents* g_moveEvents;
Items::Items()
{
items.reserve(6000);
nameToItems.reserve(6000);
}
void Items::clear()
{
items.clear();
nameToItems.clear();
}
bool Items::reload()
{
clear();
if (!loadItems()) {
return false;
}
g_moveEvents->reload();
return true;
}
bool Items::loadItems()
{
ScriptReader script;
if (!script.open("data/items/items.srv")) {
return false;
}
std::string identifier;
uint16_t id = 0;
while (true) {
script.nextToken();
if (script.Token == ENDOFFILE) {
break;
}
if (script.Token != IDENTIFIER) {
script.error("Identifier expected");
return false;
}
identifier = script.getIdentifier();
script.readSymbol('=');
if (identifier == "typeid") {
id = script.readNumber();
if (id >= items.size()) {
items.resize(id + 1);
}
if (items[id].id) {
script.error("item type already defined");
return false;
}
items[id].id = id;
} else if (identifier == "name") {
items[id].name = script.readString();
} else if (identifier == "description") {
items[id].description = script.readString();
} else if (identifier == "flags") {
script.readSymbol('{');
while (true) {
while (true) {
script.nextToken();
if (script.Token == SPECIAL) {
break;
}
identifier = script.getIdentifier();
if (identifier == "bank") {
items[id].group = ITEM_GROUP_GROUND;
} else if (identifier == "clip") {
items[id].alwaysOnTop = true;
items[id].alwaysOnTopOrder = 1;
} else if (identifier == "bottom") {
items[id].alwaysOnTop = true;
items[id].alwaysOnTopOrder = 2;
} else if (identifier == "top") {
items[id].alwaysOnTop = true;
items[id].alwaysOnTopOrder = 3;
} else if (identifier == "container") {
items[id].type = ITEM_TYPE_CONTAINER;
} else if (identifier == "chest") {
items[id].type = ITEM_TYPE_CHEST;
} else if (identifier == "cumulative") {
items[id].stackable = true;
} else if (identifier == "changeuse") {
items[id].changeUse = true;
} else if (identifier == "forceuse") {
items[id].forceUse = true;
} else if (identifier == "key") {
items[id].type = ITEM_TYPE_KEY;
items[id].group = ITEM_GROUP_KEY;
} else if (identifier == "door") {
items[id].type = ITEM_TYPE_DOOR;
} else if (identifier == "bed") {
items[id].type = ITEM_TYPE_BED;
} else if (identifier == "rune") {
items[id].type = ITEM_TYPE_RUNE;
} else if (identifier == "depot") {
items[id].type = ITEM_TYPE_DEPOT;
} else if (identifier == "mailbox") {
items[id].type = ITEM_TYPE_MAILBOX;
} else if (identifier == "allowdistread") {
items[id].allowDistRead = true;
} else if (identifier == "text") {
items[id].canReadText = true;
} else if (identifier == "write") {
items[id].canWriteText = true;
} else if (identifier == "writeonce") {
items[id].canWriteText = true;
items[id].writeOnceItemId = id;
} else if (identifier == "fluidcontainer") {
items[id].group = ITEM_GROUP_FLUID;
} else if (identifier == "splash") {
items[id].group = ITEM_GROUP_SPLASH;
} else if (identifier == "unpass") {
items[id].blockSolid = true;
} else if (identifier == "unmove") {
items[id].moveable = false;
} else if (identifier == "unthrow") {
items[id].blockProjectile = true;
} else if (identifier == "unlay") {
items[id].allowPickupable = false;
} else if (identifier == "avoid") {
items[id].blockPathFind = true;
} else if (identifier == "magicfield") {
items[id].type = ITEM_TYPE_MAGICFIELD;
items[id].group = ITEM_GROUP_MAGICFIELD;
} else if (identifier == "take") {
items[id].pickupable = true;
} else if (identifier == "hang") {
items[id].isHangable = true;
} else if (identifier == "hooksouth") {
items[id].isHorizontal = true;
} else if (identifier == "hookeast") {
items[id].isVertical = true;
} else if (identifier == "rotate") {
items[id].rotatable = true;
} else if (identifier == "destroy") {
items[id].destroy = true;
} else if (identifier == "corpse") {
items[id].corpse = true;
} else if (identifier == "expire") {
items[id].stopTime = false;
} else if (identifier == "expirestop") {
items[id].stopTime = true;
} else if (identifier == "weapon") {
items[id].group = ITEM_GROUP_WEAPON;
} else if (identifier == "shield") {
items[id].weaponType = WEAPON_SHIELD;
} else if (identifier == "distance") {
items[id].weaponType = WEAPON_DISTANCE;
} else if (identifier == "wand") {
items[id].weaponType = WEAPON_WAND;
} else if (identifier == "ammo") {
items[id].weaponType = WEAPON_AMMO;
} else if (identifier == "armor") {
items[id].group = ITEM_GROUP_ARMOR;
} else if (identifier == "height") {
items[id].hasHeight = true;
} else if (identifier == "disguise") {
items[id].disguise = true;
} else if (identifier == "showdetail") {
items[id].showDuration = true;
} else if (identifier == "noreplace") {
items[id].replaceable = false;
} else if (identifier == "collisionevent") {
items[id].collisionEvent = true;
} else if (identifier == "separationevent") {
items[id].separationEvent = true;
} else if (identifier == "useevent") {
items[id].useEvent = true;
} else if (identifier == "distuse") {
items[id].distUse = true;
} else if (identifier == "multiuse") {
items[id].multiUseEvent = true;
} else {
script.error("Unknown flag");
return false;
}
}
if (script.getSpecial() == '}') {
break;
}
if (script.Token != SPECIAL || script.getSpecial() != ',') {
continue;
}
}
} else if (identifier == "attributes") {
script.readSymbol('{');
while (true) {
while (true) {
script.nextToken();
if (script.Token == SPECIAL) {
break;
}
identifier = script.getIdentifier();
script.readSymbol('=');
if (identifier == "waypoints") {
items[id].speed = script.readNumber();
} else if (identifier == "capacity") {
items[id].maxItems = script.readNumber();
} else if (identifier == "changetarget") {
items[id].transformToOnUse = script.readNumber();
} else if (identifier == "nutrition") {
items[id].nutrition = script.readNumber();
} else if (identifier == "maxlength") {
items[id].maxTextLen = script.readNumber();
} else if (identifier == "fluidsource") {
items[id].fluidSource = getFluidType(script.readIdentifier());
} else if (identifier == "avoiddamagetypes") {
items[id].combatType = getCombatType(script.readIdentifier());
} else if (identifier == "damagetype") {
items[id].damageType = getCombatType(script.readIdentifier());
} else if (identifier == "attackstrength") {
items[id].attackStrength = script.readNumber();
} else if (identifier == "attackvariation") {
items[id].attackVariation = script.readNumber();
} else if (identifier == "manaconsumption") {
items[id].manaConsumption = script.readNumber();
} else if (identifier == "minimumlevel") {
items[id].minReqLevel = script.readNumber();
items[id].wieldInfo |= WIELDINFO_LEVEL;
} else if (identifier == "vocations") {
int32_t vocations = script.readNumber();
items[id].vocations = vocations;
std::list<std::string> vocationStringList;
if (hasBitSet(VOCATION_SORCERER, vocations)) {
vocationStringList.push_back("sorcerer");
}
if (hasBitSet(VOCATION_DRUID, vocations)) {
vocationStringList.push_back("druid");
}
if (hasBitSet(VOCATION_PALADIN, vocations)) {
vocationStringList.push_back("paladin");
}
if (hasBitSet(VOCATION_KNIGHT, vocations)) {
vocationStringList.push_back("knight");
}
std::string vocationString;
for (const std::string& str : vocationStringList) {
if (!vocationString.empty()) {
if (str != vocationStringList.back()) {
vocationString.push_back(',');
vocationString.push_back(' ');
} else {
vocationString += " and ";
}
}
vocationString += str;
vocationString.push_back('s');
}
items[id].wieldInfo |= WIELDINFO_VOCREQ;
items[id].vocationString = vocationString;
} else if (identifier == "weaponspecialeffect") {
items[id].weaponSpecialEffect = script.readNumber();
} else if (identifier == "beddirection") {
items[id].bedPartnerDir = getDirection(script.readIdentifier());
} else if (identifier == "bedtarget") {
items[id].transformToOnUse = script.readNumber();
} else if (identifier == "bedfree") {
items[id].transformToFree = script.readNumber();
} else if (identifier == "weight") {
items[id].weight = script.readNumber();
} else if (identifier == "rotatetarget") {
items[id].rotateTo = script.readNumber();
} else if (identifier == "destroytarget") {
items[id].destroyTarget = script.readNumber();
} else if (identifier == "slottype") {
identifier = asLowerCaseString(script.readIdentifier());
if (identifier == "head") {
items[id].slotPosition |= SLOTP_HEAD;
} else if (identifier == "body") {
items[id].slotPosition |= SLOTP_ARMOR;
} else if (identifier == "legs") {
items[id].slotPosition |= SLOTP_LEGS;
} else if (identifier == "feet") {
items[id].slotPosition |= SLOTP_FEET;
} else if (identifier == "backpack") {
items[id].slotPosition |= SLOTP_BACKPACK;
} else if (identifier == "twohanded") {
items[id].slotPosition |= SLOTP_TWO_HAND;
} else if (identifier == "righthand") {
items[id].slotPosition &= ~SLOTP_LEFT;
} else if (identifier == "lefthand") {
items[id].slotPosition &= ~SLOTP_RIGHT;
} else if (identifier == "necklace") {
items[id].slotPosition |= SLOTP_NECKLACE;
} else if (identifier == "ring") {
items[id].slotPosition |= SLOTP_RING;
} else if (identifier == "ammo") {
items[id].slotPosition |= SLOTP_AMMO;
} else if (identifier == "hand") {
items[id].slotPosition |= SLOTP_HAND;
} else {
script.error("Unknown slot position");
return false;
}
} else if (identifier == "speedboost") {
items[id].getAbilities().speed = script.readNumber();
} else if (identifier == "fistboost") {
items[id].getAbilities().skills[SKILL_FIST] = script.readNumber();
} else if (identifier == "swordboost") {
items[id].getAbilities().skills[SKILL_SWORD] = script.readNumber();
} else if (identifier == "clubboost") {
items[id].getAbilities().skills[SKILL_CLUB] = script.readNumber();
} else if (identifier == "axeboost") {
items[id].getAbilities().skills[SKILL_AXE] = script.readNumber();
} else if (identifier == "shieldboost") {
items[id].getAbilities().skills[SKILL_SHIELD] = script.readNumber();
} else if (identifier == "distanceboost") {
items[id].getAbilities().skills[SKILL_DISTANCE] = script.readNumber();
} else if (identifier == "magicboost") {
items[id].getAbilities().stats[STAT_MAGICPOINTS] = script.readNumber();
} else if (identifier == "percenthp") {
items[id].getAbilities().statsPercent[STAT_MAXHITPOINTS] = script.readNumber();
} else if (identifier == "percentmp") {
items[id].getAbilities().statsPercent[STAT_MAXMANAPOINTS] = script.readNumber();
} else if (identifier == "suppressdrunk") {
if (script.readNumber()) {
items[id].getAbilities().conditionSuppressions |= CONDITION_DRUNK;
}
} else if (identifier == "invisible") {
if (script.readNumber()) {
items[id].getAbilities().invisible = true;
}
} else if (identifier == "manashield") {
if (script.readNumber()) {
items[id].getAbilities().manaShield = true;
}
} else if (identifier == "healthticks") {
Abilities& abilities = items[id].getAbilities();
abilities.regeneration = true;
abilities.healthTicks = script.readNumber();
} else if (identifier == "healthgain") {
Abilities& abilities = items[id].getAbilities();
abilities.regeneration = true;
abilities.healthGain = script.readNumber();
} else if (identifier == "manaticks") {
Abilities& abilities = items[id].getAbilities();
abilities.regeneration = true;
abilities.manaTicks = script.readNumber();
} else if (identifier == "managain") {
Abilities& abilities = items[id].getAbilities();
abilities.regeneration = true;
abilities.manaGain = script.readNumber();
} else if (identifier == "absorbmagic") {
int32_t percent = script.readNumber();
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += percent;
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += percent;
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += percent;
} else if (identifier == "absorbenergy") {
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += script.readNumber();
} else if (identifier == "absorbfire") {
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += script.readNumber();
} else if (identifier == "absorbpoison") {
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += script.readNumber();
} else if (identifier == "absorblifedrain") {
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += script.readNumber();
} else if (identifier == "absorbmanadrain") {
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += script.readNumber();
} else if (identifier == "absorbphysical") {
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += script.readNumber();
} else if (identifier == "absorbhealing") {
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += script.readNumber();
} else if (identifier == "absorbundefined") {
items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += script.readNumber();
} else if (identifier == "absorbfirefield") {
items[id].getAbilities().fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += static_cast<int16_t>(script.readNumber());
} else if (identifier == "brightness") {
items[id].lightLevel = script.readNumber();
} else if (identifier == "lightcolor") {
items[id].lightColor = script.readNumber();
} else if (identifier == "totalexpiretime") {
items[id].decayTime = script.readNumber();
} else if (identifier == "expiretarget") {
items[id].decayTo = script.readNumber();
} else if (identifier == "totaluses") {
items[id].charges = script.readNumber();
} else if (identifier == "weapontype") {
identifier = script.readIdentifier();
if (identifier == "sword") {
items[id].weaponType = WEAPON_SWORD;
} else if (identifier == "club") {
items[id].weaponType = WEAPON_CLUB;
} else if (identifier == "axe") {
items[id].weaponType = WEAPON_AXE;
} else if (identifier == "shield") {
items[id].weaponType = WEAPON_SHIELD;
} else if (identifier == "distance") {
items[id].weaponType = WEAPON_DISTANCE;
} else if (identifier == "wand") {
items[id].weaponType = WEAPON_WAND;
} else if (identifier == "ammunition") {
items[id].weaponType = WEAPON_AMMO;
} else {
script.error("Unknown weapon type");
return false;
}
} else if (identifier == "attack") {
items[id].attack = script.readNumber();
} else if (identifier == "defense") {
items[id].defense = script.readNumber();
} else if (identifier == "range") {
items[id].shootRange = static_cast<uint8_t>(script.readNumber());
} else if (identifier == "ammotype") {
items[id].ammoType = getAmmoType(script.readIdentifier());
if (items[id].ammoType == AMMO_NONE) {
script.error("Unknown ammo type");
return false;
}
} else if (identifier == "missileeffect") {
items[id].shootType = static_cast<ShootType_t>(script.readNumber());
} else if (identifier == "fragility") {
items[id].fragility = script.readNumber();
} else if (identifier == "armorvalue") {
items[id].armor = script.readNumber();
} else if (identifier == "disguisetarget") {
items[id].disguiseId = script.readNumber();
} else if (identifier == "equiptarget") {
items[id].transformEquipTo = script.readNumber();
} else if (identifier == "deequiptarget") {
items[id].transformDeEquipTo = script.readNumber();
} else {
script.error("Unknown attribute");
return false;
}
}
if (script.getSpecial() == '}') {
break;
}
if (script.Token != SPECIAL || script.getSpecial() != ',') {
continue;
}
}
} else if (identifier == "magicfield") {
script.readSymbol('{');
CombatType_t combatType = COMBAT_NONE;
ConditionDamage* conditionDamage = nullptr;
int32_t cycles = 0;
int32_t hit_damage = 0;
while (true) {
while (true) {
script.nextToken();
if (script.Token == SPECIAL) {
break;
}
identifier = script.getIdentifier();
script.readSymbol('=');
if (identifier == "type") {
identifier = script.readIdentifier();
if (identifier == "fire") {
conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE);
combatType = COMBAT_FIREDAMAGE;
items[id].combatType = combatType;
items[id].conditionDamage.reset(conditionDamage);
} else if (identifier == "energy") {
conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_ENERGY);
combatType = COMBAT_ENERGYDAMAGE;
items[id].combatType = combatType;
items[id].conditionDamage.reset(conditionDamage);
} else if (identifier == "poison") {
conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON);
conditionDamage->setParam(CONDITION_PARAM_DELAYED, true);
combatType = COMBAT_EARTHDAMAGE;
items[id].combatType = combatType;
items[id].conditionDamage.reset(conditionDamage);
} else {
script.error("unknown magicfield type");
return false;
}
} else if (identifier == "count") {
cycles = script.readNumber();
} else if (identifier == "damage") {
hit_damage = script.readNumber();
} else {
script.error("unknown identifier");
return false;
}
}
if (script.getSpecial() == '}') {
break;
}
if (script.Token != SPECIAL || script.getSpecial() != ',') {
continue;
}
}
int32_t count = 3;
if (combatType == COMBAT_FIREDAMAGE) {
cycles /= 10;
count = 8;
} else if (combatType == COMBAT_ENERGYDAMAGE) {
cycles /= 20;
count = 10;
}
conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles);
conditionDamage->setParam(CONDITION_PARAM_COUNT, count);
conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count);
conditionDamage->setParam(CONDITION_PARAM_HIT_DAMAGE, hit_damage);
conditionDamage->setParam(CONDITION_PARAM_FIELD, 1);
}
}
script.close();
items.shrink_to_fit();
for (ItemType& type : items) {
std::string& name = type.name;
extractArticleAndName(name, type.article, type.name);
nameToItems.insert({ asLowerCaseString(type.name), type.id });
if (!name.empty()) {
if (type.stackable) {
type.showCount = true;
type.pluralName = pluralizeString(name);
}
}
}
return true;
}
ItemType& Items::getItemType(size_t id)
{
if (id < items.size()) {
return items[id];
}
return items.front();
}
const ItemType& Items::getItemType(size_t id) const
{
if (id < items.size()) {
return items[id];
}
return items.front();
}
uint16_t Items::getItemIdByName(const std::string& name)
{
auto result = nameToItems.find(asLowerCaseString(name));
if (result == nameToItems.end())
return 0;
return result->second;
}

326
src/items.h Normal file
View File

@@ -0,0 +1,326 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_ITEMS_H_4E2221634ABA45FE85BA50F710669B3C
#define FS_ITEMS_H_4E2221634ABA45FE85BA50F710669B3C
#include "const.h"
#include "enums.h"
#include "position.h"
#include "fileloader.h"
enum SlotPositionBits : uint32_t {
SLOTP_WHEREEVER = 0xFFFFFFFF,
SLOTP_HEAD = 1 << 0,
SLOTP_NECKLACE = 1 << 1,
SLOTP_BACKPACK = 1 << 2,
SLOTP_ARMOR = 1 << 3,
SLOTP_RIGHT = 1 << 4,
SLOTP_LEFT = 1 << 5,
SLOTP_LEGS = 1 << 6,
SLOTP_FEET = 1 << 7,
SLOTP_RING = 1 << 8,
SLOTP_AMMO = 1 << 9,
SLOTP_DEPOT = 1 << 10,
SLOTP_TWO_HAND = 1 << 11,
SLOTP_HAND = (SLOTP_LEFT | SLOTP_RIGHT)
};
enum ItemTypes_t {
ITEM_TYPE_NONE,
ITEM_TYPE_DEPOT,
ITEM_TYPE_MAILBOX,
ITEM_TYPE_CONTAINER,
ITEM_TYPE_DOOR,
ITEM_TYPE_MAGICFIELD,
ITEM_TYPE_TELEPORT,
ITEM_TYPE_BED,
ITEM_TYPE_KEY,
ITEM_TYPE_RUNE,
ITEM_TYPE_CHEST,
ITEM_TYPE_LAST
};
enum itemgroup_t {
ITEM_GROUP_NONE,
ITEM_GROUP_GROUND,
ITEM_GROUP_WEAPON,
ITEM_GROUP_AMMUNITION,
ITEM_GROUP_ARMOR,
ITEM_GROUP_CHARGES,
ITEM_GROUP_TELEPORT,
ITEM_GROUP_MAGICFIELD,
ITEM_GROUP_WRITEABLE,
ITEM_GROUP_KEY,
ITEM_GROUP_SPLASH,
ITEM_GROUP_FLUID,
ITEM_GROUP_DOOR,
ITEM_GROUP_DEPRECATED,
ITEM_GROUP_LAST
};
struct Abilities {
uint32_t healthGain = 0;
uint32_t healthTicks = 0;
uint32_t manaGain = 0;
uint32_t manaTicks = 0;
uint32_t conditionImmunities = 0;
uint32_t conditionSuppressions = 0;
//stats modifiers
int32_t stats[STAT_LAST + 1] = { 0 };
int32_t statsPercent[STAT_LAST + 1] = { 0 };
//extra skill modifiers
int32_t skills[SKILL_LAST + 1] = { 0 };
int32_t speed = 0;
// field damage abilities modifiers
int16_t fieldAbsorbPercent[COMBAT_COUNT] = { 0 };
//damage abilities modifiers
int16_t absorbPercent[COMBAT_COUNT] = { 0 };
bool manaShield = false;
bool invisible = false;
bool regeneration = false;
};
class ConditionDamage;
class ItemType
{
public:
ItemType() = default;
//non-copyable
ItemType(const ItemType& other) = delete;
ItemType& operator=(const ItemType& other) = delete;
ItemType(ItemType&& other) = default;
ItemType& operator=(ItemType&& other) = default;
bool isGroundTile() const {
return group == ITEM_GROUP_GROUND;
}
bool isContainer() const {
return type == ITEM_TYPE_CONTAINER;
}
bool isChest() const {
return type == ITEM_TYPE_CHEST;
}
bool isSplash() const {
return group == ITEM_GROUP_SPLASH;
}
bool isFluidContainer() const {
return group == ITEM_GROUP_FLUID;
}
bool isDoor() const {
return (type == ITEM_TYPE_DOOR);
}
bool isMagicField() const {
return (type == ITEM_TYPE_MAGICFIELD);
}
bool isTeleport() const {
return (type == ITEM_TYPE_TELEPORT);
}
bool isKey() const {
return (type == ITEM_TYPE_KEY);
}
bool isDepot() const {
return (type == ITEM_TYPE_DEPOT);
}
bool isMailbox() const {
return (type == ITEM_TYPE_MAILBOX);
}
bool isBed() const {
return (type == ITEM_TYPE_BED);
}
bool isRune() const {
return type == ITEM_TYPE_RUNE;
}
bool hasSubType() const {
return (isFluidContainer() || isSplash() || stackable || charges != 0);
}
Abilities& getAbilities() {
if (!abilities) {
abilities.reset(new Abilities());
}
return *abilities;
}
std::string getPluralName() const {
if (!pluralName.empty()) {
return pluralName;
}
if (showCount == 0) {
return name;
}
std::string str;
str.reserve(name.length() + 1);
str.assign(name);
str += 's';
return str;
}
itemgroup_t group = ITEM_GROUP_NONE;
ItemTypes_t type = ITEM_TYPE_NONE;
uint16_t id = 0;
bool stackable = false;
std::string name;
std::string article;
std::string pluralName;
std::string description;
std::string runeSpellName;
std::string vocationString;
std::unique_ptr<Abilities> abilities;
std::unique_ptr<ConditionDamage> conditionDamage;
uint32_t weight = 0;
uint32_t decayTime = 0;
uint32_t wieldInfo = 0;
uint32_t minReqLevel = 0;
uint32_t minReqMagicLevel = 0;
uint32_t charges = 0;
int32_t attackStrength = 0;
int32_t attackVariation = 0;
int32_t manaConsumption = 0;
int32_t vocations = 0;
int32_t decayTo = -1;
int32_t attack = 0;
int32_t defense = 0;
int32_t extraDefense = 0;
int32_t armor = 0;
int32_t rotateTo = 0;
int32_t runeMagLevel = 0;
int32_t runeLevel = 0;
int32_t nutrition = 0;
int32_t destroyTarget = 0;
CombatType_t combatType = COMBAT_NONE;
CombatType_t damageType = COMBAT_NONE;
uint16_t transformToOnUse = 0;
uint16_t transformToFree = 0;
uint16_t disguiseId = 0;
uint16_t destroyTo = 0;
uint16_t maxTextLen = 0;
uint16_t writeOnceItemId = 0;
uint16_t transformEquipTo = 0;
uint16_t transformDeEquipTo = 0;
uint16_t maxItems = 8;
uint16_t slotPosition = SLOTP_RIGHT | SLOTP_LEFT | SLOTP_AMMO;
uint16_t speed = 0;
MagicEffectClasses magicEffect = CONST_ME_NONE;
Direction bedPartnerDir = DIRECTION_NONE;
WeaponType_t weaponType = WEAPON_NONE;
Ammo_t ammoType = AMMO_NONE;
ShootType_t shootType = CONST_ANI_NONE;
RaceType_t corpseType = RACE_NONE;
FluidTypes_t fluidSource = FLUID_NONE;
uint8_t fragility = 0;
uint8_t alwaysOnTopOrder = 0;
uint8_t lightLevel = 0;
uint8_t lightColor = 0;
uint8_t shootRange = 1;
uint8_t weaponSpecialEffect = 0;
bool collisionEvent = false;
bool separationEvent = false;
bool useEvent = false;
bool multiUseEvent = false;
bool distUse = false;
bool disguise = false;
bool forceUse = false;
bool changeUse = false;
bool destroy = false;
bool corpse = false;
bool hasHeight = false;
bool walkStack = true;
bool blockSolid = false;
bool blockPickupable = false;
bool blockProjectile = false;
bool blockPathFind = false;
bool allowPickupable = true;
bool showDuration = false;
bool showCharges = false;
bool showAttributes = false;
bool replaceable = true;
bool pickupable = false;
bool rotatable = false;
bool useable = false;
bool moveable = true;
bool alwaysOnTop = false;
bool canReadText = false;
bool canWriteText = false;
bool isVertical = false;
bool isHorizontal = false;
bool isHangable = false;
bool allowDistRead = false;
bool lookThrough = false;
bool stopTime = false;
bool showCount = true;
};
class Items
{
public:
using nameMap = std::unordered_multimap<std::string, uint16_t>;
Items();
// non-copyable
Items(const Items&) = delete;
Items& operator=(const Items&) = delete;
bool reload();
void clear();
const ItemType& operator[](size_t id) const {
return getItemType(id);
}
const ItemType& getItemType(size_t id) const;
ItemType& getItemType(size_t id);
uint16_t getItemIdByName(const std::string& name);
bool loadItems();
inline size_t size() const {
return items.size();
}
nameMap nameToItems;
protected:
std::vector<ItemType> items;
};
#endif

62
src/lockfree.h Normal file
View File

@@ -0,0 +1,62 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_LOCKFREE_H_8C707AEB7C7235A2FBC5D4EDDF03B008
#define FS_LOCKFREE_H_8C707AEB7C7235A2FBC5D4EDDF03B008
#if _MSC_FULL_VER >= 190023918 // Workaround for VS2015 Update 2. Boost.Lockfree is a header-only library, so this should be safe to do.
#define _ENABLE_ATOMIC_ALIGNMENT_FIX
#endif
#include <boost/lockfree/stack.hpp>
template <typename T, size_t CAPACITY>
class LockfreePoolingAllocator : public std::allocator<T>
{
public:
template <typename U>
explicit constexpr LockfreePoolingAllocator(const U&) {}
typedef T value_type;
T* allocate(size_t) const {
T* p; // NOTE: p doesn't have to be initialized
if (!getFreeList().pop(p)) {
//Acquire memory without calling the constructor of T
p = static_cast<T*>(operator new (sizeof(T)));
}
return p;
}
void deallocate(T* p, size_t) const {
if (!getFreeList().bounded_push(p)) {
//Release memory without calling the destructor of T
//(it has already been called at this point)
operator delete(p);
}
}
private:
typedef boost::lockfree::stack<T*, boost::lockfree::capacity<CAPACITY>> FreeList;
static FreeList& getFreeList() {
static FreeList freeList;
return freeList;
}
};
#endif

11417
src/luascript.cpp Normal file

File diff suppressed because it is too large Load Diff

1241
src/luascript.h Normal file

File diff suppressed because it is too large Load Diff

183
src/mailbox.cpp Normal file
View File

@@ -0,0 +1,183 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "mailbox.h"
#include "game.h"
#include "player.h"
#include "iologindata.h"
#include "town.h"
extern Game g_game;
ReturnValue Mailbox::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t, Creature*) const
{
const Item* item = thing.getItem();
if (item && Mailbox::canSend(item)) {
return RETURNVALUE_NOERROR;
}
return RETURNVALUE_NOTPOSSIBLE;
}
ReturnValue Mailbox::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const
{
maxQueryCount = std::max<uint32_t>(1, count);
return RETURNVALUE_NOERROR;
}
ReturnValue Mailbox::queryRemove(const Thing&, uint32_t, uint32_t) const
{
return RETURNVALUE_NOTPOSSIBLE;
}
Cylinder* Mailbox::queryDestination(int32_t&, const Thing&, Item**, uint32_t&)
{
return this;
}
void Mailbox::addThing(Thing* thing)
{
return addThing(0, thing);
}
void Mailbox::addThing(int32_t, Thing* thing)
{
Item* item = thing->getItem();
if (item && Mailbox::canSend(item)) {
sendItem(item);
}
}
void Mailbox::updateThing(Thing*, uint16_t, uint32_t)
{
//
}
void Mailbox::replaceThing(uint32_t, Thing*)
{
//
}
void Mailbox::removeThing(Thing*, uint32_t)
{
//
}
void Mailbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t)
{
getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT);
}
void Mailbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t)
{
getParent()->postRemoveNotification(thing, newParent, index, LINK_PARENT);
}
bool Mailbox::sendItem(Item* item) const
{
std::string receiver;
std::string townName;
if (!getDestination(item, receiver, townName)) {
return false;
}
if (receiver.empty() || townName.empty()) {
return false;
}
Town* town = g_game.map.towns.getTown(townName);
if (!town) {
return false;
}
Player* player = g_game.getPlayerByName(receiver);
if (player) {
DepotLocker* depotLocker = player->getDepotLocker(town->getID(), true);
if (depotLocker) {
if (g_game.internalMoveItem(item->getParent(), depotLocker, INDEX_WHEREEVER,
item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) {
g_game.transformItem(item, item->getID() + 1);
player->onReceiveMail();
return true;
}
}
} else {
Player tmpPlayer(nullptr);
if (!IOLoginData::loadPlayerByName(&tmpPlayer, receiver)) {
return false;
}
DepotLocker* depotLocker = tmpPlayer.getDepotLocker(town->getID(), true);
if (depotLocker) {
if (g_game.internalMoveItem(item->getParent(), depotLocker, INDEX_WHEREEVER,
item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) {
g_game.transformItem(item, item->getID() + 1);
IOLoginData::savePlayer(&tmpPlayer);
return true;
}
}
}
return false;
}
bool Mailbox::getDestination(Item* item, std::string& name, std::string& town) const
{
const Container* container = item->getContainer();
if (container) {
for (Item* containerItem : container->getItemList()) {
if (containerItem->getID() == ITEM_LABEL && getDestination(containerItem, name, town)) {
return true;
}
}
return false;
}
const std::string& text = item->getText();
if (text.empty()) {
return false;
}
std::istringstream iss(text, std::istringstream::in);
std::string temp;
uint32_t currentLine = 1;
while (getline(iss, temp, '\n')) {
if (currentLine == 1) {
name = temp;
} else if (currentLine == 2) {
town = temp;
} else {
break;
}
++currentLine;
}
trimString(name);
trimString(town);
return true;
}
bool Mailbox::canSend(const Item* item)
{
return item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER;
}

66
src/mailbox.h Normal file
View File

@@ -0,0 +1,66 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_MAILBOX_H_D231C6BE8D384CAAA3AE410C1323F9DB
#define FS_MAILBOX_H_D231C6BE8D384CAAA3AE410C1323F9DB
#include "item.h"
#include "cylinder.h"
#include "const.h"
class Mailbox final : public Item, public Cylinder
{
public:
explicit Mailbox(uint16_t itemId) : Item(itemId) {}
Mailbox* getMailbox() final {
return this;
}
const Mailbox* getMailbox() const final {
return this;
}
//cylinder implementations
ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count,
uint32_t flags, Creature* actor = nullptr) const final;
ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count,
uint32_t& maxQueryCount, uint32_t flags) const final;
ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final;
Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem,
uint32_t& flags) final;
void addThing(Thing* thing) final;
void addThing(int32_t index, Thing* thing) final;
void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final;
void replaceThing(uint32_t index, Thing* thing) final;
void removeThing(Thing* thing, uint32_t count) final;
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
private:
bool getDestination(Item* item, std::string& name, std::string& town) const;
bool sendItem(Item* item) const;
static bool canSend(const Item* item);
};
#endif

1028
src/map.cpp Normal file

File diff suppressed because it is too large Load Diff

287
src/map.h Normal file
View File

@@ -0,0 +1,287 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_MAP_H_E3953D57C058461F856F5221D359DAFA
#define FS_MAP_H_E3953D57C058461F856F5221D359DAFA
#include "position.h"
#include "item.h"
#include "fileloader.h"
#include "tools.h"
#include "tile.h"
#include "town.h"
#include "house.h"
#include "spawn.h"
class Creature;
class Player;
class Game;
class Tile;
class Map;
static constexpr int32_t MAP_MAX_LAYERS = 16;
struct FindPathParams;
struct AStarNode {
AStarNode* parent;
int_fast32_t f;
uint16_t x, y;
};
static constexpr int32_t MAX_NODES = 512;
static constexpr int32_t MAP_NORMALWALKCOST = 10;
static constexpr int32_t MAP_DIAGONALWALKCOST = 25;
class AStarNodes
{
public:
AStarNodes(uint32_t x, uint32_t y);
AStarNode* createOpenNode(AStarNode* parent, uint32_t x, uint32_t y, int_fast32_t f);
AStarNode* getBestNode();
void closeNode(AStarNode* node);
void openNode(AStarNode* node);
int_fast32_t getClosedNodes() const;
AStarNode* getNodeByPosition(uint32_t x, uint32_t y);
static int_fast32_t getMapWalkCost(AStarNode* node, const Position& neighborPos);
static int_fast32_t getTileWalkCost(const Creature& creature, const Tile* tile);
private:
AStarNode nodes[MAX_NODES];
bool openNodes[MAX_NODES];
std::unordered_map<uint32_t, AStarNode*> nodeTable;
size_t curNode;
int_fast32_t closedNodes;
};
typedef std::map<Position, SpectatorVec> SpectatorCache;
static constexpr int32_t FLOOR_BITS = 3;
static constexpr int32_t FLOOR_SIZE = (1 << FLOOR_BITS);
static constexpr int32_t FLOOR_MASK = (FLOOR_SIZE - 1);
struct Floor {
constexpr Floor() = default;
~Floor();
// non-copyable
Floor(const Floor&) = delete;
Floor& operator=(const Floor&) = delete;
Tile* tiles[FLOOR_SIZE][FLOOR_SIZE] = {};
};
class FrozenPathingConditionCall;
class QTreeLeafNode;
class QTreeNode
{
public:
constexpr QTreeNode() = default;
virtual ~QTreeNode();
// non-copyable
QTreeNode(const QTreeNode&) = delete;
QTreeNode& operator=(const QTreeNode&) = delete;
bool isLeaf() const {
return leaf;
}
QTreeLeafNode* getLeaf(uint32_t x, uint32_t y);
template<typename Leaf, typename Node>
inline static Leaf getLeafStatic(Node node, uint32_t x, uint32_t y)
{
do {
node = node->child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)];
if (!node) {
return nullptr;
}
x <<= 1;
y <<= 1;
} while (!node->leaf);
return static_cast<Leaf>(node);
}
QTreeLeafNode* createLeaf(uint32_t x, uint32_t y, uint32_t level);
protected:
QTreeNode* child[4] = {};
bool leaf = false;
friend class Map;
};
class QTreeLeafNode final : public QTreeNode
{
public:
QTreeLeafNode() { leaf = true; newLeaf = true; }
~QTreeLeafNode();
// non-copyable
QTreeLeafNode(const QTreeLeafNode&) = delete;
QTreeLeafNode& operator=(const QTreeLeafNode&) = delete;
Floor* createFloor(uint32_t z);
Floor* getFloor(uint8_t z) const {
return array[z];
}
void addCreature(Creature* c);
void removeCreature(Creature* c);
protected:
static bool newLeaf;
QTreeLeafNode* leafS = nullptr;
QTreeLeafNode* leafE = nullptr;
Floor* array[MAP_MAX_LAYERS] = {};
CreatureVector creature_list;
CreatureVector player_list;
friend class Map;
friend class QTreeNode;
};
/**
* Map class.
* Holds all the actual map-data
*/
class Map
{
public:
static constexpr int32_t maxViewportX = 11; //min value: maxClientViewportX + 1
static constexpr int32_t maxViewportY = 11; //min value: maxClientViewportY + 1
static constexpr int32_t maxClientViewportX = 8;
static constexpr int32_t maxClientViewportY = 6;
uint32_t clean() const;
/**
* Load a map.
* \returns true if the map was loaded successfully
*/
bool loadMap(const std::string& identifier, bool loadHouses);
/**
* Save a map.
* \returns true if the map was saved successfully
*/
static bool save();
/**
* Get a single tile.
* \returns A pointer to that tile.
*/
Tile* getTile(uint16_t x, uint16_t y, uint8_t z) const;
inline Tile* getTile(const Position& pos) const {
return getTile(pos.x, pos.y, pos.z);
}
/**
* Set a single tile.
*/
void setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile);
void setTile(const Position& pos, Tile* newTile) {
setTile(pos.x, pos.y, pos.z, newTile);
}
/**
* Place a creature on the map
* \param centerPos The position to place the creature
* \param creature Creature to place on the map
* \param extendedPos If true, the creature will in first-hand be placed 2 tiles away
* \param forceLogin If true, placing the creature will not fail becase of obstacles (creatures/chests)
*/
bool placeCreature(const Position& centerPos, Creature* creature, bool extendedPos = false, bool forceLogin = false);
void moveCreature(Creature& creature, Tile& newTile, bool forceTeleport = false);
void getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor = false, bool onlyPlayers = false,
int32_t minRangeX = 0, int32_t maxRangeX = 0,
int32_t minRangeY = 0, int32_t maxRangeY = 0);
void clearSpectatorCache();
/**
* Checks if you can throw an object to that position
* \param fromPos from Source point
* \param toPos Destination point
* \param rangex maximum allowed range horizontially
* \param rangey maximum allowed range vertically
* \param checkLineOfSight checks if there is any blocking objects in the way
* \returns The result if you can throw there or not
*/
bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true,
int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY) const;
/**
* Checks if path is clear from fromPos to toPos
* Notice: This only checks a straight line if the path is clear, for path finding use getPathTo.
* \param fromPos from Source point
* \param toPos Destination point
* \param floorCheck if true then view is not clear if fromPos.z is not the same as toPos.z
* \returns The result if there is no obstacles
*/
bool isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const;
bool checkSightLine(const Position& fromPos, const Position& toPos) const;
const Tile* canWalkTo(const Creature& creature, const Position& pos) const;
bool getPathMatching(const Creature& creature, std::forward_list<Direction>& dirList,
const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const;
std::map<std::string, Position> waypoints;
QTreeLeafNode* getQTNode(uint16_t x, uint16_t y) {
return QTreeNode::getLeafStatic<QTreeLeafNode*, QTreeNode*>(&root, x, y);
}
Spawns spawns;
Towns towns;
Houses houses;
protected:
SpectatorCache spectatorCache;
SpectatorCache playersSpectatorCache;
QTreeNode root;
std::string spawnfile;
std::string housefile;
uint32_t width = 0;
uint32_t height = 0;
// Actually scans the map for spectators
void getSpectatorsInternal(SpectatorVec& list, const Position& centerPos,
int32_t minRangeX, int32_t maxRangeX,
int32_t minRangeY, int32_t maxRangeY,
int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const;
friend class Game;
friend class IOMap;
};
#endif

2165
src/monster.cpp Normal file

File diff suppressed because it is too large Load Diff

274
src/monster.h Normal file
View File

@@ -0,0 +1,274 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0
#define FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0
#include "tile.h"
#include "monsters.h"
class Creature;
class Game;
class Spawn;
class Combat;
typedef std::unordered_set<Creature*> CreatureHashSet;
typedef std::list<Creature*> CreatureList;
enum TargetSearchType_t {
TARGETSEARCH_ANY,
TARGETSEARCH_RANDOM,
TARGETSEARCH_NEAREST,
TARGETSEARCH_WEAKEST,
TARGETSEARCH_MOSTDAMAGE,
};
class Monster final : public Creature
{
public:
static Monster* createMonster(const std::string& name);
static int32_t despawnRange;
static int32_t despawnRadius;
explicit Monster(MonsterType* mtype);
~Monster();
// non-copyable
Monster(const Monster&) = delete;
Monster& operator=(const Monster&) = delete;
Monster* getMonster() final {
return this;
}
const Monster* getMonster() const final {
return this;
}
void setID() final {
if (id == 0) {
id = monsterAutoID++;
}
}
void removeList() final;
void addList() final;
const std::string& getName() const final {
return mType->name;
}
const std::string& getNameDescription() const final {
return mType->nameDescription;
}
std::string getDescription(int32_t) const final {
return strDescription + '.';
}
const Position& getMasterPos() const {
return masterPos;
}
void setMasterPos(Position pos) {
masterPos = pos;
}
RaceType_t getRace() const final {
return mType->info.race;
}
int32_t getArmor() const final {
int32_t armor = mType->info.armor;
if (armor > 1) {
return rand() % (mType->info.armor >> 1) + (mType->info.armor >> 1);
}
return armor;
}
int32_t getDefense() final;
bool isPushable() const final {
return mType->info.pushable && baseSpeed != 0;
}
bool isAttackable() const final {
return mType->info.isAttackable;
}
bool canPushItems() const {
return mType->info.canPushItems;
}
bool canPushCreatures() const {
return mType->info.canPushCreatures;
}
bool isHostile() const {
return mType->info.isHostile;
}
bool canSee(const Position& pos) const final;
bool canSeeInvisibility() const final {
return isImmune(CONDITION_INVISIBLE);
}
uint32_t getManaCost() const {
return mType->info.manaCost;
}
void setSpawn(Spawn* spawn) {
this->spawn = spawn;
}
void onAttackedCreature(Creature* creature) final;
void onCreatureAppear(Creature* creature, bool isLogin) final;
void onRemoveCreature(Creature* creature, bool isLogout) final;
void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) final;
void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) final;
void drainHealth(Creature* attacker, int32_t damage) final;
void changeHealth(int32_t healthChange, bool sendHealthChange = true) final;
void onWalk() final;
bool getNextStep(Direction& direction, uint32_t& flags) final;
void onFollowCreatureComplete(const Creature* creature) final;
void onThink(uint32_t interval) final;
bool challengeCreature(Creature* creature) final;
bool convinceCreature(Creature* creature) final;
void setNormalCreatureLight() final;
bool getCombatValues(int32_t& min, int32_t& max) final;
void doAttacking(uint32_t interval) final;
bool searchTarget(TargetSearchType_t searchType);
bool selectTarget(Creature* creature);
const CreatureList& getTargetList() const {
return targetList;
}
const CreatureHashSet& getFriendList() const {
return friendList;
}
bool isTarget(const Creature* creature) const;
bool isFleeing() const {
return !isSummon() && getHealth() <= mType->info.runAwayHealth;
}
bool getDistanceStep(const Position& targetPos, Direction& direction, bool flee = false);
bool isTargetNearby() const {
return stepDuration >= 1;
}
BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage,
bool checkDefense = false, bool checkArmor = false, bool field = false);
static uint32_t monsterAutoID;
private:
CreatureHashSet friendList;
CreatureList targetList;
std::string strDescription;
MonsterType* mType;
Spawn* spawn = nullptr;
int64_t lifetime = 0;
int64_t nextDanceStepRound = 0;
int64_t earliestAttackTime = 0;
int64_t earliestWakeUpTime = 0;
int64_t earliestDanceTime = 0;
uint32_t targetChangeTicks = 0;
int32_t minCombatValue = 0;
int32_t maxCombatValue = 0;
int32_t targetChangeCooldown = 0;
int32_t stepDuration = 0;
Position masterPos;
bool isIdle = true;
bool isMasterInRange = false;
bool egibleToDance = true;
void onCreatureEnter(Creature* creature);
void onCreatureLeave(Creature* creature);
void onCreatureFound(Creature* creature, bool pushFront = false);
void updateLookDirection();
void addFriend(Creature* creature);
void removeFriend(Creature* creature);
void addTarget(Creature* creature, bool pushFront = false);
void removeTarget(Creature* creature);
void updateTargetList();
void clearTargetList();
void clearFriendList();
void death(Creature* lastHitCreature) final;
Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final;
void setIdle(bool idle);
void updateIdleStatus();
bool getIdleStatus() const {
return isIdle;
}
void onAddCondition(ConditionType_t type) final;
void onEndCondition(ConditionType_t type) final;
void onCreatureConvinced(const Creature* convincer, const Creature* creature) final;
bool canUseAttack(const Position& pos, const Creature* target) const;
bool getRandomStep(const Position& creaturePos, Direction& direction) const;
bool getDanceStep(const Position& creaturePos, Direction& direction,
bool keepAttack = true, bool keepDistance = true);
bool isInSpawnRange(const Position& pos) const;
bool canWalkTo(Position pos, Direction direction) const;
static bool pushItem(Item* item);
static void pushItems(Tile* tile);
static bool pushCreature(Creature* creature);
static void pushCreatures(Tile* tile);
void onThinkTarget(uint32_t interval);
void onThinkYell(uint32_t interval);
void onThinkDefense(uint32_t interval);
bool isFriend(const Creature* creature) const;
bool isOpponent(const Creature* creature) const;
uint64_t getLostExperience() const final {
return skillLoss ? mType->info.experience : 0;
}
uint16_t getLookCorpse() const final {
return mType->info.lookcorpse;
}
void dropLoot(Container* corpse, Creature* lastHitCreature) final;
uint32_t getDamageImmunities() const final {
return mType->info.damageImmunities;
}
uint32_t getConditionImmunities() const final {
return mType->info.conditionImmunities;
}
void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const final;
bool useCacheMap() const final {
return true;
}
friend class LuaScriptInterface;
friend class AreaSpawnEvent;
friend class Combat;
friend class Creature;
};
#endif

1070
src/monsters.cpp Normal file

File diff suppressed because it is too large Load Diff

201
src/monsters.h Normal file
View File

@@ -0,0 +1,201 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_MONSTERS_H_776E8327BCE2450EB7C4A260785E6C0D
#define FS_MONSTERS_H_776E8327BCE2450EB7C4A260785E6C0D
#include "creature.h"
#define MAX_LOOTCHANCE 1000
#define MAX_STATICWALK 100
struct LootBlock {
uint16_t id;
uint32_t countmax;
uint32_t chance;
//optional
int32_t subType;
int32_t actionId;
std::string text;
std::vector<LootBlock> childLoot;
LootBlock() {
id = 0;
countmax = 0;
chance = 0;
subType = -1;
actionId = -1;
}
};
struct summonBlock_t {
std::string name;
uint32_t chance;
uint32_t max;
bool force = false;
};
class BaseSpell;
struct spellBlock_t {
constexpr spellBlock_t() = default;
~spellBlock_t();
spellBlock_t(const spellBlock_t& other) = delete;
spellBlock_t& operator=(const spellBlock_t& other) = delete;
spellBlock_t(spellBlock_t&& other) :
spell(other.spell),
chance(other.chance),
range(other.range),
minCombatValue(other.minCombatValue),
maxCombatValue(other.maxCombatValue),
attack(other.attack),
skill(other.skill),
combatSpell(other.combatSpell) {
other.spell = nullptr;
}
BaseSpell* spell = nullptr;
uint32_t chance = 100;
uint32_t range = 0;
int32_t minCombatValue = 0;
int32_t maxCombatValue = 0;
int32_t attack = 0;
int32_t skill = 0;
bool combatSpell = false;
};
struct voiceBlock_t {
std::string text;
bool yellText;
};
class MonsterType
{
struct MonsterInfo {
LuaScriptInterface* scriptInterface;
std::map<CombatType_t, int32_t> elementMap;
std::vector<voiceBlock_t> voiceVector;
std::vector<LootBlock> lootItems;
std::vector<std::string> scripts;
std::vector<spellBlock_t> attackSpells;
std::vector<spellBlock_t> defenseSpells;
std::vector<summonBlock_t> summons;
Skulls_t skull = SKULL_NONE;
Outfit_t outfit = {};
RaceType_t race = RACE_BLOOD;
LightInfo light = {};
uint16_t lookcorpse = 0;
uint64_t experience = 0;
uint32_t manaCost = 0;
uint32_t maxSummons = 0;
uint32_t changeTargetSpeed = 0;
uint32_t conditionImmunities = 0;
uint32_t damageImmunities = 0;
uint32_t baseSpeed = 70;
int32_t creatureAppearEvent = -1;
int32_t creatureDisappearEvent = -1;
int32_t creatureMoveEvent = -1;
int32_t creatureSayEvent = -1;
int32_t thinkEvent = -1;
int32_t targetDistance = 1;
int32_t runAwayHealth = 0;
int32_t health = 100;
int32_t healthMax = 100;
int32_t changeTargetChance = 0;
int32_t strategyNearestEnemy = 0;
int32_t strategyWeakestEnemy = 0;
int32_t strategyMostDamageEnemy = 0;
int32_t strategyRandomEnemy = 0;
int32_t armor = 0;
int32_t defense = 0;
int32_t attack = 0;
int32_t skill = 0;
int32_t poison = 0;
bool canPushItems = false;
bool canPushCreatures = false;
bool pushable = true;
bool isSummonable = false;
bool isIllusionable = false;
bool isConvinceable = false;
bool isAttackable = true;
bool isHostile = true;
bool hiddenHealth = false;
};
public:
MonsterType() = default;
// non-copyable
MonsterType(const MonsterType&) = delete;
MonsterType& operator=(const MonsterType&) = delete;
std::string name;
std::string nameDescription;
MonsterInfo info;
void createLoot(Container* corpse);
bool createLootContainer(Container* parent, const LootBlock& lootblock);
std::vector<Item*> createLootItem(const LootBlock& lootBlock);
};
class Monsters
{
public:
Monsters() = default;
// non-copyable
Monsters(const Monsters&) = delete;
Monsters& operator=(const Monsters&) = delete;
bool loadFromXml(bool reloading = false);
bool isLoaded() const {
return loaded;
}
bool reload();
MonsterType* getMonsterType(const std::string& name);
static uint32_t getLootRandom();
private:
ConditionDamage* getDamageCondition(ConditionType_t conditionType, int32_t cycles, int32_t count, int32_t max_count);
bool deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, const std::string& description = "");
bool loadMonster(const std::string& file, const std::string& monsterName, std::list<std::pair<MonsterType*, std::string>>& monsterScriptList, bool reloading = false);
void loadLootContainer(const pugi::xml_node& node, LootBlock&);
bool loadLootItem(const pugi::xml_node& node, LootBlock&);
std::map<std::string, MonsterType> monsters;
std::unique_ptr<LuaScriptInterface> scriptInterface;
bool loaded = false;
};
#endif

876
src/movement.cpp Normal file
View File

@@ -0,0 +1,876 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "game.h"
#include "pugicast.h"
#include "movement.h"
extern Game g_game;
extern Vocations g_vocations;
MoveEvents::MoveEvents() :
scriptInterface("MoveEvents Interface")
{
scriptInterface.initState();
}
MoveEvents::~MoveEvents()
{
clear();
}
void MoveEvents::clearMap(MoveListMap& map)
{
std::unordered_set<MoveEvent*> set;
for (const auto& it : map) {
const MoveEventList& moveEventList = it.second;
for (const auto& i : moveEventList.moveEvent) {
for (MoveEvent* moveEvent : i) {
set.insert(moveEvent);
}
}
}
map.clear();
for (MoveEvent* moveEvent : set) {
delete moveEvent;
}
}
void MoveEvents::clear()
{
clearMap(itemIdMap);
clearMap(movementIdMap);
for (const auto& it : positionMap) {
const MoveEventList& moveEventList = it.second;
for (const auto& i : moveEventList.moveEvent) {
for (MoveEvent* moveEvent : i) {
delete moveEvent;
}
}
}
positionMap.clear();
scriptInterface.reInitState();
}
LuaScriptInterface& MoveEvents::getScriptInterface()
{
return scriptInterface;
}
std::string MoveEvents::getScriptBaseName() const
{
return "movements";
}
Event* MoveEvents::getEvent(const std::string& nodeName)
{
if (strcasecmp(nodeName.c_str(), "movevent") != 0) {
return nullptr;
}
return new MoveEvent(&scriptInterface);
}
bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node)
{
MoveEvent* moveEvent = static_cast<MoveEvent*>(event); //event is guaranteed to be a MoveEvent
const MoveEvent_t eventType = moveEvent->getEventType();
if (eventType == MOVE_EVENT_ADD_ITEM || eventType == MOVE_EVENT_REMOVE_ITEM) {
pugi::xml_attribute tileItemAttribute = node.attribute("tileitem");
if (tileItemAttribute && pugi::cast<uint16_t>(tileItemAttribute.value()) == 1) {
switch (eventType) {
case MOVE_EVENT_ADD_ITEM:
moveEvent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE);
break;
case MOVE_EVENT_REMOVE_ITEM:
moveEvent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE);
break;
default:
break;
}
}
}
pugi::xml_attribute attr;
if ((attr = node.attribute("itemid"))) {
int32_t id = pugi::cast<int32_t>(attr.value());
addEvent(moveEvent, id, itemIdMap);
if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) {
ItemType& it = Item::items.getItemType(id);
it.wieldInfo = moveEvent->getWieldInfo();
it.minReqLevel = moveEvent->getReqLevel();
it.minReqMagicLevel = moveEvent->getReqMagLv();
it.vocationString = moveEvent->getVocationString();
}
} else if ((attr = node.attribute("fromid"))) {
uint32_t id = pugi::cast<uint32_t>(attr.value());
uint32_t endId = pugi::cast<uint32_t>(node.attribute("toid").value());
addEvent(moveEvent, id, itemIdMap);
if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) {
ItemType& it = Item::items.getItemType(id);
it.wieldInfo = moveEvent->getWieldInfo();
it.minReqLevel = moveEvent->getReqLevel();
it.minReqMagicLevel = moveEvent->getReqMagLv();
it.vocationString = moveEvent->getVocationString();
while (++id <= endId) {
addEvent(moveEvent, id, itemIdMap);
ItemType& tit = Item::items.getItemType(id);
tit.wieldInfo = moveEvent->getWieldInfo();
tit.minReqLevel = moveEvent->getReqLevel();
tit.minReqMagicLevel = moveEvent->getReqMagLv();
tit.vocationString = moveEvent->getVocationString();
}
} else {
while (++id <= endId) {
addEvent(moveEvent, id, itemIdMap);
}
}
} else if ((attr = node.attribute("movementid"))) {
addEvent(moveEvent, pugi::cast<int32_t>(attr.value()), movementIdMap);
} else if ((attr = node.attribute("frommovementid"))) {
uint32_t id = pugi::cast<uint32_t>(attr.value());
uint32_t endId = pugi::cast<uint32_t>(node.attribute("tomovementid").value());
addEvent(moveEvent, id, movementIdMap);
while (++id <= endId) {
addEvent(moveEvent, id, movementIdMap);
}
} else if ((attr = node.attribute("pos"))) {
std::vector<int32_t> posList = vectorAtoi(explodeString(attr.as_string(), ";"));
if (posList.size() < 3) {
return false;
}
Position pos(posList[0], posList[1], posList[2]);
addEvent(moveEvent, pos, positionMap);
} else {
return false;
}
return true;
}
void MoveEvents::addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map)
{
auto it = map.find(id);
if (it == map.end()) {
MoveEventList moveEventList;
moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent);
map[id] = moveEventList;
} else {
std::list<MoveEvent*>& moveEventList = it->second.moveEvent[moveEvent->getEventType()];
for (MoveEvent* existingMoveEvent : moveEventList) {
if (existingMoveEvent->getSlot() == moveEvent->getSlot()) {
std::cout << "[Warning - MoveEvents::addEvent] Duplicate move event found: " << id << std::endl;
}
}
moveEventList.push_back(moveEvent);
}
}
MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType, slots_t slot)
{
uint32_t slotp;
switch (slot) {
case CONST_SLOT_HEAD: slotp = SLOTP_HEAD; break;
case CONST_SLOT_NECKLACE: slotp = SLOTP_NECKLACE; break;
case CONST_SLOT_BACKPACK: slotp = SLOTP_BACKPACK; break;
case CONST_SLOT_ARMOR: slotp = SLOTP_ARMOR; break;
case CONST_SLOT_RIGHT: slotp = SLOTP_RIGHT; break;
case CONST_SLOT_LEFT: slotp = SLOTP_LEFT; break;
case CONST_SLOT_LEGS: slotp = SLOTP_LEGS; break;
case CONST_SLOT_FEET: slotp = SLOTP_FEET; break;
case CONST_SLOT_AMMO: slotp = SLOTP_AMMO; break;
case CONST_SLOT_RING: slotp = SLOTP_RING; break;
default: slotp = 0; break;
}
auto it = itemIdMap.find(item->getID());
if (it != itemIdMap.end()) {
std::list<MoveEvent*>& moveEventList = it->second.moveEvent[eventType];
for (MoveEvent* moveEvent : moveEventList) {
if ((moveEvent->getSlot() & slotp) != 0) {
return moveEvent;
}
}
}
return nullptr;
}
MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType)
{
MoveListMap::iterator it;
if (item->hasAttribute(ITEM_ATTRIBUTE_MOVEMENTID)) {
it = movementIdMap.find(item->getMovementId());
if (it != movementIdMap.end()) {
std::list<MoveEvent*>& moveEventList = it->second.moveEvent[eventType];
if (!moveEventList.empty()) {
return *moveEventList.begin();
}
}
}
if (!item->hasCollisionEvent() && !item->hasSeparationEvent()) {
return nullptr;
}
it = itemIdMap.find(item->getID());
if (it != itemIdMap.end()) {
std::list<MoveEvent*>& moveEventList = it->second.moveEvent[eventType];
if (!moveEventList.empty()) {
return *moveEventList.begin();
}
}
return nullptr;
}
void MoveEvents::addEvent(MoveEvent* moveEvent, const Position& pos, MovePosListMap& map)
{
auto it = map.find(pos);
if (it == map.end()) {
MoveEventList moveEventList;
moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent);
map[pos] = moveEventList;
} else {
std::list<MoveEvent*>& moveEventList = it->second.moveEvent[moveEvent->getEventType()];
if (!moveEventList.empty()) {
std::cout << "[Warning - MoveEvents::addEvent] Duplicate move event found: " << pos << std::endl;
}
moveEventList.push_back(moveEvent);
}
}
MoveEvent* MoveEvents::getEvent(const Tile* tile, MoveEvent_t eventType)
{
auto it = positionMap.find(tile->getPosition());
if (it != positionMap.end()) {
std::list<MoveEvent*>& moveEventList = it->second.moveEvent[eventType];
if (!moveEventList.empty()) {
return *moveEventList.begin();
}
}
return nullptr;
}
uint32_t MoveEvents::onCreatureMove(Creature* creature, const Tile* tile, MoveEvent_t eventType)
{
const Position& pos = tile->getPosition();
uint32_t ret = 1;
MoveEvent* moveEvent = getEvent(tile, eventType);
if (moveEvent) {
ret &= moveEvent->fireStepEvent(creature, nullptr, pos);
}
for (size_t i = tile->getFirstIndex(), j = tile->getLastIndex(); i < j; ++i) {
Thing* thing = tile->getThing(i);
if (!thing) {
continue;
}
Item* tileItem = thing->getItem();
if (!tileItem) {
continue;
}
moveEvent = getEvent(tileItem, eventType);
if (moveEvent) {
ret &= moveEvent->fireStepEvent(creature, tileItem, pos);
}
}
return ret;
}
uint32_t MoveEvents::onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck)
{
MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_EQUIP, slot);
if (!moveEvent) {
return 1;
}
return moveEvent->fireEquip(player, item, slot, isCheck);
}
uint32_t MoveEvents::onPlayerDeEquip(Player* player, Item* item, slots_t slot)
{
MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_DEEQUIP, slot);
if (!moveEvent) {
return 1;
}
return moveEvent->fireEquip(player, item, slot, true);
}
uint32_t MoveEvents::onItemMove(Item* item, Tile* tile, bool isAdd)
{
MoveEvent_t eventType1, eventType2;
if (isAdd) {
eventType1 = MOVE_EVENT_ADD_ITEM;
eventType2 = MOVE_EVENT_ADD_ITEM_ITEMTILE;
} else {
eventType1 = MOVE_EVENT_REMOVE_ITEM;
eventType2 = MOVE_EVENT_REMOVE_ITEM_ITEMTILE;
}
uint32_t ret = 1;
MoveEvent* moveEvent = getEvent(tile, eventType1);
if (moveEvent) {
ret &= moveEvent->fireAddRemItem(item, nullptr, tile->getPosition());
}
moveEvent = getEvent(item, eventType1);
if (moveEvent) {
ret &= moveEvent->fireAddRemItem(item, nullptr, tile->getPosition());
}
for (size_t i = tile->getFirstIndex(), j = tile->getLastIndex(); i < j; ++i) {
Thing* thing = tile->getThing(i);
if (!thing) {
continue;
}
Item* tileItem = thing->getItem();
if (!tileItem || tileItem == item) {
continue;
}
moveEvent = getEvent(tileItem, eventType2);
if (moveEvent) {
ret &= moveEvent->fireAddRemItem(item, tileItem, tile->getPosition());
}
}
return ret;
}
MoveEvent::MoveEvent(LuaScriptInterface* interface) : Event(interface) {}
MoveEvent::MoveEvent(const MoveEvent* copy) :
Event(copy),
eventType(copy->eventType),
stepFunction(copy->stepFunction),
moveFunction(copy->moveFunction),
equipFunction(copy->equipFunction),
slot(copy->slot),
reqLevel(copy->reqLevel),
reqMagLevel(copy->reqMagLevel),
premium(copy->premium),
vocationString(copy->vocationString),
wieldInfo(copy->wieldInfo),
vocEquipMap(copy->vocEquipMap) {}
std::string MoveEvent::getScriptEventName() const
{
switch (eventType) {
case MOVE_EVENT_STEP_IN: return "onStepIn";
case MOVE_EVENT_STEP_OUT: return "onStepOut";
case MOVE_EVENT_EQUIP: return "onEquip";
case MOVE_EVENT_DEEQUIP: return "onDeEquip";
case MOVE_EVENT_ADD_ITEM: return "onAddItem";
case MOVE_EVENT_REMOVE_ITEM: return "onRemoveItem";
default:
std::cout << "[Error - MoveEvent::getScriptEventName] Invalid event type" << std::endl;
return std::string();
}
}
bool MoveEvent::configureEvent(const pugi::xml_node& node)
{
pugi::xml_attribute eventAttr = node.attribute("event");
if (!eventAttr) {
std::cout << "[Error - MoveEvent::configureMoveEvent] Missing event" << std::endl;
return false;
}
std::string tmpStr = asLowerCaseString(eventAttr.as_string());
if (tmpStr == "stepin") {
eventType = MOVE_EVENT_STEP_IN;
} else if (tmpStr == "stepout") {
eventType = MOVE_EVENT_STEP_OUT;
} else if (tmpStr == "equip") {
eventType = MOVE_EVENT_EQUIP;
} else if (tmpStr == "deequip") {
eventType = MOVE_EVENT_DEEQUIP;
} else if (tmpStr == "additem") {
eventType = MOVE_EVENT_ADD_ITEM;
} else if (tmpStr == "removeitem") {
eventType = MOVE_EVENT_REMOVE_ITEM;
} else {
std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << eventAttr.as_string() << std::endl;
return false;
}
if (eventType == MOVE_EVENT_EQUIP || eventType == MOVE_EVENT_DEEQUIP) {
pugi::xml_attribute slotAttribute = node.attribute("slot");
if (slotAttribute) {
tmpStr = asLowerCaseString(slotAttribute.as_string());
if (tmpStr == "head") {
slot = SLOTP_HEAD;
} else if (tmpStr == "necklace") {
slot = SLOTP_NECKLACE;
} else if (tmpStr == "backpack") {
slot = SLOTP_BACKPACK;
} else if (tmpStr == "armor") {
slot = SLOTP_ARMOR;
} else if (tmpStr == "right-hand") {
slot = SLOTP_RIGHT;
} else if (tmpStr == "left-hand") {
slot = SLOTP_LEFT;
} else if (tmpStr == "hand" || tmpStr == "shield") {
slot = SLOTP_RIGHT | SLOTP_LEFT;
} else if (tmpStr == "legs") {
slot = SLOTP_LEGS;
} else if (tmpStr == "feet") {
slot = SLOTP_FEET;
} else if (tmpStr == "ring") {
slot = SLOTP_RING;
} else if (tmpStr == "ammo") {
slot = SLOTP_AMMO;
} else {
std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " << slotAttribute.as_string() << std::endl;
}
}
wieldInfo = 0;
pugi::xml_attribute levelAttribute = node.attribute("level");
if (levelAttribute) {
reqLevel = pugi::cast<uint32_t>(levelAttribute.value());
if (reqLevel > 0) {
wieldInfo |= WIELDINFO_LEVEL;
}
}
pugi::xml_attribute magLevelAttribute = node.attribute("maglevel");
if (magLevelAttribute) {
reqMagLevel = pugi::cast<uint32_t>(magLevelAttribute.value());
if (reqMagLevel > 0) {
wieldInfo |= WIELDINFO_MAGLV;
}
}
pugi::xml_attribute premiumAttribute = node.attribute("premium");
if (premiumAttribute) {
premium = premiumAttribute.as_bool();
if (premium) {
wieldInfo |= WIELDINFO_PREMIUM;
}
}
//Gather vocation information
std::list<std::string> vocStringList;
for (auto vocationNode : node.children()) {
pugi::xml_attribute vocationNameAttribute = vocationNode.attribute("name");
if (!vocationNameAttribute) {
continue;
}
int32_t vocationId = g_vocations.getVocationId(vocationNameAttribute.as_string());
if (vocationId != -1) {
vocEquipMap[vocationId] = true;
if (vocationNode.attribute("showInDescription").as_bool(true)) {
vocStringList.push_back(asLowerCaseString(vocationNameAttribute.as_string()));
}
}
}
if (!vocEquipMap.empty()) {
wieldInfo |= WIELDINFO_VOCREQ;
}
for (const std::string& str : vocStringList) {
if (!vocationString.empty()) {
if (str != vocStringList.back()) {
vocationString.push_back(',');
vocationString.push_back(' ');
} else {
vocationString += " and ";
}
}
vocationString += str;
vocationString.push_back('s');
}
}
return true;
}
bool MoveEvent::loadFunction(const pugi::xml_attribute& attr)
{
const char* functionName = attr.as_string();
if (strcasecmp(functionName, "onstepinfield") == 0) {
stepFunction = StepInField;
} else if (strcasecmp(functionName, "onstepoutfield") == 0) {
stepFunction = StepOutField;
} else if (strcasecmp(functionName, "onaddfield") == 0) {
moveFunction = AddItemField;
} else if (strcasecmp(functionName, "onremovefield") == 0) {
moveFunction = RemoveItemField;
} else if (strcasecmp(functionName, "onequipitem") == 0) {
equipFunction = EquipItem;
} else if (strcasecmp(functionName, "ondeequipitem") == 0) {
equipFunction = DeEquipItem;
} else {
std::cout << "[Warning - MoveEvent::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl;
return false;
}
scripted = false;
return true;
}
MoveEvent_t MoveEvent::getEventType() const
{
return eventType;
}
void MoveEvent::setEventType(MoveEvent_t type)
{
eventType = type;
}
uint32_t MoveEvent::StepInField(Creature* creature, Item* item, const Position&)
{
MagicField* field = item->getMagicField();
if (field) {
field->onStepInField(creature);
return 1;
}
return LUA_ERROR_ITEM_NOT_FOUND;
}
uint32_t MoveEvent::StepOutField(Creature*, Item*, const Position&)
{
return 1;
}
uint32_t MoveEvent::AddItemField(Item* item, Item*, const Position&)
{
if (MagicField* field = item->getMagicField()) {
Tile* tile = item->getTile();
if (CreatureVector* creatures = tile->getCreatures()) {
for (Creature* creature : *creatures) {
field->onStepInField(creature);
}
}
return 1;
}
return LUA_ERROR_ITEM_NOT_FOUND;
}
uint32_t MoveEvent::RemoveItemField(Item*, Item*, const Position&)
{
return 1;
}
uint32_t MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isCheck)
{
if (player->isItemAbilityEnabled(slot)) {
return 1;
}
if (!player->hasFlag(PlayerFlag_IgnoreWeaponCheck) && moveEvent->getWieldInfo() != 0) {
if (player->getLevel() < moveEvent->getReqLevel() || player->getMagicLevel() < moveEvent->getReqMagLv()) {
return 0;
}
if (moveEvent->isPremium() && !player->isPremium()) {
return 0;
}
const VocEquipMap& vocEquipMap = moveEvent->getVocEquipMap();
if (!vocEquipMap.empty() && vocEquipMap.find(player->getVocationId()) == vocEquipMap.end()) {
return 0;
}
}
if (isCheck) {
return 1;
}
const ItemType& it = Item::items[item->getID()];
if (it.transformEquipTo != 0) {
Item* newItem = g_game.transformItem(item, it.transformEquipTo);
g_game.startDecay(newItem);
} else {
player->setItemAbility(slot, true);
}
if (!it.abilities) {
return 1;
}
if (it.abilities->invisible) {
Condition* condition = Condition::createCondition(static_cast<ConditionId_t>(slot), CONDITION_INVISIBLE, -1, 0);
player->addCondition(condition);
}
if (it.abilities->manaShield) {
Condition* condition = Condition::createCondition(static_cast<ConditionId_t>(slot), CONDITION_MANASHIELD, -1, 0);
player->addCondition(condition);
}
if (it.abilities->speed != 0) {
g_game.changeSpeed(player, it.abilities->speed);
}
if (it.abilities->conditionSuppressions != 0) {
player->addConditionSuppressions(it.abilities->conditionSuppressions);
player->sendIcons();
}
if (it.abilities->regeneration) {
Condition* condition = Condition::createCondition(static_cast<ConditionId_t>(slot), CONDITION_REGENERATION, -1, 0);
if (it.abilities->healthGain != 0) {
condition->setParam(CONDITION_PARAM_HEALTHGAIN, it.abilities->healthGain);
}
if (it.abilities->healthTicks != 0) {
condition->setParam(CONDITION_PARAM_HEALTHTICKS, it.abilities->healthTicks);
}
if (it.abilities->manaGain != 0) {
condition->setParam(CONDITION_PARAM_MANAGAIN, it.abilities->manaGain);
}
if (it.abilities->manaTicks != 0) {
condition->setParam(CONDITION_PARAM_MANATICKS, it.abilities->manaTicks);
}
player->addCondition(condition);
}
//skill modifiers
bool needUpdateSkills = false;
for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) {
if (it.abilities->skills[i]) {
needUpdateSkills = true;
player->setVarSkill(static_cast<skills_t>(i), it.abilities->skills[i]);
}
}
if (needUpdateSkills) {
player->sendSkills();
}
//stat modifiers
bool needUpdateStats = false;
for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) {
if (it.abilities->stats[s]) {
needUpdateStats = true;
player->setVarStats(static_cast<stats_t>(s), it.abilities->stats[s]);
}
if (it.abilities->statsPercent[s]) {
needUpdateStats = true;
player->setVarStats(static_cast<stats_t>(s), static_cast<int32_t>(player->getDefaultStats(static_cast<stats_t>(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f)));
}
}
if (needUpdateStats) {
player->sendStats();
}
return 1;
}
uint32_t MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots_t slot, bool)
{
if (!player->isItemAbilityEnabled(slot)) {
return 1;
}
player->setItemAbility(slot, false);
const ItemType& it = Item::items[item->getID()];
if (it.transformDeEquipTo != 0) {
g_game.transformItem(item, it.transformDeEquipTo);
g_game.startDecay(item);
}
if (!it.abilities) {
return 1;
}
if (it.abilities->invisible) {
player->removeCondition(CONDITION_INVISIBLE, static_cast<ConditionId_t>(slot));
}
if (it.abilities->manaShield) {
player->removeCondition(CONDITION_MANASHIELD, static_cast<ConditionId_t>(slot));
}
if (it.abilities->speed != 0) {
g_game.changeSpeed(player, -it.abilities->speed);
}
if (it.abilities->conditionSuppressions != 0) {
player->removeConditionSuppressions(it.abilities->conditionSuppressions);
player->sendIcons();
}
if (it.abilities->regeneration) {
player->removeCondition(CONDITION_REGENERATION, static_cast<ConditionId_t>(slot));
}
//skill modifiers
bool needUpdateSkills = false;
for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) {
if (it.abilities->skills[i] != 0) {
needUpdateSkills = true;
player->setVarSkill(static_cast<skills_t>(i), -it.abilities->skills[i]);
}
}
if (needUpdateSkills) {
player->sendSkills();
}
//stat modifiers
bool needUpdateStats = false;
for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) {
if (it.abilities->stats[s]) {
needUpdateStats = true;
player->setVarStats(static_cast<stats_t>(s), -it.abilities->stats[s]);
}
if (it.abilities->statsPercent[s]) {
needUpdateStats = true;
player->setVarStats(static_cast<stats_t>(s), -static_cast<int32_t>(player->getDefaultStats(static_cast<stats_t>(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f)));
}
}
if (needUpdateStats) {
player->sendStats();
}
return 1;
}
uint32_t MoveEvent::fireStepEvent(Creature* creature, Item* item, const Position& pos)
{
if (scripted) {
return executeStep(creature, item, pos);
} else {
return stepFunction(creature, item, pos);
}
}
bool MoveEvent::executeStep(Creature* creature, Item* item, const Position& pos)
{
//onStepIn(creature, item, pos, fromPosition)
//onStepOut(creature, item, pos, fromPosition)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - MoveEvent::executeStep] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata<Creature>(L, creature);
LuaScriptInterface::setCreatureMetatable(L, -1, creature);
LuaScriptInterface::pushThing(L, item);
LuaScriptInterface::pushPosition(L, pos);
LuaScriptInterface::pushPosition(L, creature->getLastPosition());
return scriptInterface->callFunction(4);
}
uint32_t MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool boolean)
{
if (scripted) {
return executeEquip(player, item, slot);
} else {
return equipFunction(this, player, item, slot, boolean);
}
}
bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot)
{
//onEquip(player, item, slot)
//onDeEquip(player, item, slot)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - MoveEvent::executeEquip] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushUserdata<Player>(L, player);
LuaScriptInterface::setMetatable(L, -1, "Player");
LuaScriptInterface::pushThing(L, item);
lua_pushnumber(L, slot);
return scriptInterface->callFunction(3);
}
uint32_t MoveEvent::fireAddRemItem(Item* item, Item* tileItem, const Position& pos)
{
if (scripted) {
return executeAddRemItem(item, tileItem, pos);
} else {
return moveFunction(item, tileItem, pos);
}
}
bool MoveEvent::executeAddRemItem(Item* item, Item* tileItem, const Position& pos)
{
//onaddItem(moveitem, tileitem, pos)
//onRemoveItem(moveitem, tileitem, pos)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - MoveEvent::executeAddRemItem] Call stack overflow" << std::endl;
return false;
}
ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);
lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);
LuaScriptInterface::pushThing(L, item);
LuaScriptInterface::pushThing(L, tileItem);
LuaScriptInterface::pushPosition(L, pos);
return scriptInterface->callFunction(3);
}

168
src/movement.h Normal file
View File

@@ -0,0 +1,168 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49
#define FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49
#include "baseevents.h"
#include "item.h"
#include "luascript.h"
enum MoveEvent_t {
MOVE_EVENT_STEP_IN,
MOVE_EVENT_STEP_OUT,
MOVE_EVENT_EQUIP,
MOVE_EVENT_DEEQUIP,
MOVE_EVENT_ADD_ITEM,
MOVE_EVENT_REMOVE_ITEM,
MOVE_EVENT_ADD_ITEM_ITEMTILE,
MOVE_EVENT_REMOVE_ITEM_ITEMTILE,
MOVE_EVENT_LAST,
MOVE_EVENT_NONE
};
class MoveEvent;
struct MoveEventList {
std::list<MoveEvent*> moveEvent[MOVE_EVENT_LAST];
};
typedef std::map<uint16_t, bool> VocEquipMap;
class MoveEvents final : public BaseEvents
{
public:
MoveEvents();
~MoveEvents();
// non-copyable
MoveEvents(const MoveEvents&) = delete;
MoveEvents& operator=(const MoveEvents&) = delete;
uint32_t onCreatureMove(Creature* creature, const Tile* tile, MoveEvent_t eventType);
uint32_t onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck);
uint32_t onPlayerDeEquip(Player* player, Item* item, slots_t slot);
uint32_t onItemMove(Item* item, Tile* tile, bool isAdd);
MoveEvent* getEvent(Item* item, MoveEvent_t eventType);
protected:
typedef std::map<int32_t, MoveEventList> MoveListMap;
void clearMap(MoveListMap& map);
typedef std::map<Position, MoveEventList> MovePosListMap;
void clear() final;
LuaScriptInterface& getScriptInterface() final;
std::string getScriptBaseName() const final;
Event* getEvent(const std::string& nodeName) final;
bool registerEvent(Event* event, const pugi::xml_node& node) final;
void addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map);
void addEvent(MoveEvent* moveEvent, const Position& pos, MovePosListMap& map);
MoveEvent* getEvent(const Tile* tile, MoveEvent_t eventType);
MoveEvent* getEvent(Item* item, MoveEvent_t eventType, slots_t slot);
MoveListMap movementIdMap;
MoveListMap itemIdMap;
MovePosListMap positionMap;
LuaScriptInterface scriptInterface;
};
typedef uint32_t (StepFunction)(Creature* creature, Item* item, const Position& pos);
typedef uint32_t (MoveFunction)(Item* item, Item* tileItem, const Position& pos);
typedef uint32_t (EquipFunction)(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool boolean);
class MoveEvent final : public Event
{
public:
explicit MoveEvent(LuaScriptInterface* interface);
explicit MoveEvent(const MoveEvent* copy);
MoveEvent_t getEventType() const;
void setEventType(MoveEvent_t type);
bool configureEvent(const pugi::xml_node& node) final;
bool loadFunction(const pugi::xml_attribute& attr) final;
uint32_t fireStepEvent(Creature* creature, Item* item, const Position& pos);
uint32_t fireAddRemItem(Item* item, Item* tileItem, const Position& pos);
uint32_t fireEquip(Player* player, Item* item, slots_t slot, bool boolean);
uint32_t getSlot() const {
return slot;
}
//scripting
bool executeStep(Creature* creature, Item* item, const Position& pos);
bool executeEquip(Player* player, Item* item, slots_t slot);
bool executeAddRemItem(Item* item, Item* tileItem, const Position& pos);
//
//onEquip information
uint32_t getReqLevel() const {
return reqLevel;
}
uint32_t getReqMagLv() const {
return reqMagLevel;
}
bool isPremium() const {
return premium;
}
const std::string& getVocationString() const {
return vocationString;
}
uint32_t getWieldInfo() const {
return wieldInfo;
}
const VocEquipMap& getVocEquipMap() const {
return vocEquipMap;
}
protected:
std::string getScriptEventName() const final;
static StepFunction StepInField;
static StepFunction StepOutField;
static MoveFunction AddItemField;
static MoveFunction RemoveItemField;
static EquipFunction EquipItem;
static EquipFunction DeEquipItem;
MoveEvent_t eventType = MOVE_EVENT_NONE;
StepFunction* stepFunction = nullptr;
MoveFunction* moveFunction = nullptr;
EquipFunction* equipFunction = nullptr;
uint32_t slot = SLOTP_WHEREEVER;
//onEquip information
uint32_t reqLevel = 0;
uint32_t reqMagLevel = 0;
bool premium = false;
std::string vocationString;
uint32_t wieldInfo = 0;
VocEquipMap vocEquipMap;
};
#endif

135
src/networkmessage.cpp Normal file
View File

@@ -0,0 +1,135 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "networkmessage.h"
#include "container.h"
#include "creature.h"
std::string NetworkMessage::getString(uint16_t stringLen/* = 0*/)
{
if (stringLen == 0) {
stringLen = get<uint16_t>();
}
if (!canRead(stringLen)) {
return std::string();
}
char* v = reinterpret_cast<char*>(buffer) + info.position; //does not break strict aliasing
info.position += stringLen;
return std::string(v, stringLen);
}
Position NetworkMessage::getPosition()
{
Position pos;
pos.x = get<uint16_t>();
pos.y = get<uint16_t>();
pos.z = getByte();
return pos;
}
void NetworkMessage::addString(const std::string& value)
{
size_t stringLen = value.length();
if (!canAdd(stringLen + 2) || stringLen > 8192) {
return;
}
add<uint16_t>(stringLen);
memcpy(buffer + info.position, value.c_str(), stringLen);
info.position += stringLen;
info.length += stringLen;
}
void NetworkMessage::addDouble(double value, uint8_t precision/* = 2*/)
{
addByte(precision);
add<uint32_t>((value * std::pow(static_cast<float>(10), precision)) + std::numeric_limits<int32_t>::max());
}
void NetworkMessage::addBytes(const char* bytes, size_t size)
{
if (!canAdd(size) || size > 8192) {
return;
}
memcpy(buffer + info.position, bytes, size);
info.position += size;
info.length += size;
}
void NetworkMessage::addPaddingBytes(size_t n)
{
if (!canAdd(n)) {
return;
}
memset(buffer + info.position, 0x33, n);
info.length += n;
}
void NetworkMessage::addPosition(const Position& pos)
{
add<uint16_t>(pos.x);
add<uint16_t>(pos.y);
addByte(pos.z);
}
void NetworkMessage::addItem(uint16_t id, uint8_t count)
{
const ItemType& it = Item::items[id];
if (it.disguise) {
add<uint16_t>(it.disguiseId);
} else {
add<uint16_t>(it.id);
}
if (it.stackable) {
addByte(count);
} else if (it.isSplash() || it.isFluidContainer()) {
addByte(getLiquidColor(count));
}
}
void NetworkMessage::addItem(const Item* item)
{
const ItemType& it = Item::items[item->getID()];
if (it.disguise) {
add<uint16_t>(it.disguiseId);
} else {
add<uint16_t>(it.id);
}
if (it.stackable) {
addByte(std::min<uint16_t>(0xFF, item->getItemCount()));
} else if (it.isSplash() || it.isFluidContainer()) {
addByte(getLiquidColor(item->getFluidType()));
}
}
void NetworkMessage::addItemId(uint16_t itemId)
{
add<uint16_t>(itemId);
}

173
src/networkmessage.h Normal file
View File

@@ -0,0 +1,173 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_NETWORKMESSAGE_H_B853CFED58D1413A87ACED07B2926E03
#define FS_NETWORKMESSAGE_H_B853CFED58D1413A87ACED07B2926E03
#include "const.h"
class Item;
class Creature;
class Player;
struct Position;
class RSA;
class NetworkMessage
{
public:
typedef uint16_t MsgSize_t;
// Headers:
// 2 bytes for unencrypted message size
// 2 bytes for encrypted message size
static constexpr MsgSize_t INITIAL_BUFFER_POSITION = 4;
enum { HEADER_LENGTH = 2 };
enum { XTEA_MULTIPLE = 8 };
enum { MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - XTEA_MULTIPLE };
enum { MAX_PROTOCOL_BODY_LENGTH = MAX_BODY_LENGTH - 10 };
NetworkMessage() = default;
void reset() {
info = {};
}
// simply read functions for incoming message
uint8_t getByte() {
if (!canRead(1)) {
return 0;
}
return buffer[info.position++];
}
uint8_t getPreviousByte() {
return buffer[--info.position];
}
template<typename T>
T get() {
if (!canRead(sizeof(T))) {
return 0;
}
T v;
memcpy(&v, buffer + info.position, sizeof(T));
info.position += sizeof(T);
return v;
}
std::string getString(uint16_t stringLen = 0);
Position getPosition();
// skips count unknown/unused bytes in an incoming message
void skipBytes(int16_t count) {
info.position += count;
}
// simply write functions for outgoing message
void addByte(uint8_t value) {
if (!canAdd(1)) {
return;
}
buffer[info.position++] = value;
info.length++;
}
template<typename T>
void add(T value) {
if (!canAdd(sizeof(T))) {
return;
}
memcpy(buffer + info.position, &value, sizeof(T));
info.position += sizeof(T);
info.length += sizeof(T);
}
void addBytes(const char* bytes, size_t size);
void addPaddingBytes(size_t n);
void addString(const std::string& value);
void addDouble(double value, uint8_t precision = 2);
// write functions for complex types
void addPosition(const Position& pos);
void addItem(uint16_t id, uint8_t count);
void addItem(const Item* item);
void addItemId(uint16_t itemId);
MsgSize_t getLength() const {
return info.length;
}
void setLength(MsgSize_t newLength) {
info.length = newLength;
}
MsgSize_t getBufferPosition() const {
return info.position;
}
uint16_t getLengthHeader() const {
return static_cast<uint16_t>(buffer[0] | buffer[1] << 8);
}
bool isOverrun() const {
return info.overrun;
}
uint8_t* getBuffer() {
return buffer;
}
const uint8_t* getBuffer() const {
return buffer;
}
uint8_t* getBodyBuffer() {
info.position = 2;
return buffer + HEADER_LENGTH;
}
protected:
inline bool canAdd(size_t size) const {
return (size + info.position) < MAX_BODY_LENGTH;
}
inline bool canRead(int32_t size) {
if ((info.position + size) > (info.length + 8) || size >= (NETWORKMESSAGE_MAXSIZE - info.position)) {
info.overrun = true;
return false;
}
return true;
}
struct NetworkMessageInfo {
MsgSize_t length = 0;
MsgSize_t position = INITIAL_BUFFER_POSITION;
bool overrun = false;
};
NetworkMessageInfo info;
uint8_t buffer[NETWORKMESSAGE_MAXSIZE];
};
#endif // #ifndef __NETWORK_MESSAGE_H__

482
src/npc.cpp Normal file
View File

@@ -0,0 +1,482 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "npc.h"
#include "game.h"
#include "tools.h"
#include "position.h"
#include "player.h"
#include "spawn.h"
#include "script.h"
#include "behaviourdatabase.h"
extern Game g_game;
uint32_t Npc::npcAutoID = 0x80000000;
void Npcs::loadNpcs()
{
std::cout << ">> Loading npcs..." << std::endl;
std::vector<boost::filesystem::path> files;
getFilesInDirectory("data/npc/", ".npc", files);
for (auto file : files)
{
std::string npcName = file.filename().string();
int32_t end = npcName.find_first_of('/');
npcName = npcName.substr(end + 1, npcName.length() - end);
end = npcName.find_first_of('.');
npcName = npcName.substr(0, end);
Npc* npc = Npc::createNpc(npcName);
if (!npc) {
return;
}
g_game.placeCreature(npc, npc->getMasterPos(), false, true);
}
}
void Npcs::reload()
{
const std::map<uint32_t, Npc*>& npcs = g_game.getNpcs();
for (const auto& it : npcs) {
it.second->reload();
}
}
Npc* Npc::createNpc(const std::string& name)
{
std::unique_ptr<Npc> npc(new Npc(name));
npc->filename = "data/npc/" + name + ".npc";
if (!npc->load()) {
return nullptr;
}
return npc.release();
}
Npc::Npc(const std::string& name) :
Creature(),
filename("data/npc/" + name + ".npc"),
masterRadius(0),
staticMovementTime(0),
loaded(false),
behaviourDatabase(nullptr)
{
baseSpeed = 5;
reset();
}
Npc::~Npc()
{
reset();
}
void Npc::addList()
{
g_game.addNpc(this);
}
void Npc::removeList()
{
g_game.removeNpc(this);
}
bool Npc::load()
{
if (loaded) {
return true;
}
reset();
ScriptReader script;
if (!script.open(filename)) {
return false;
}
while (true) {
script.nextToken();
if (script.Token == ENDOFFILE) {
break;
}
if (script.Token != IDENTIFIER) {
script.error("identifier expected");
return false;
}
std::string ident = script.getIdentifier();
script.readSymbol('=');
if (ident == "name") {
name = script.readString();
} else if (ident == "outfit") {
script.readSymbol('(');
uint8_t* c;
currentOutfit.lookType = script.readNumber();
script.readSymbol(',');
if (currentOutfit.lookType > 0) {
c = script.readBytesequence();
currentOutfit.lookHead = c[0];
currentOutfit.lookBody = c[1];
currentOutfit.lookLegs = c[2];
currentOutfit.lookFeet = c[3];
} else {
currentOutfit.lookTypeEx = script.readNumber();
}
script.readSymbol(')');
} else if (ident == "home") {
script.readCoordinate(masterPos.x, masterPos.y, masterPos.z);
} else if (ident == "radius") {
masterRadius = script.readNumber();
} else if (ident == "behaviour") {
if (behaviourDatabase) {
script.error("behaviour database already defined");
return false;
}
behaviourDatabase = new BehaviourDatabase(this);
if (!behaviourDatabase->loadDatabase(script)) {
return false;
}
}
}
script.close();
return true;
}
void Npc::reset()
{
loaded = false;
focusCreature = 0;
conversationEndTime = 0;
if (behaviourDatabase) {
delete behaviourDatabase;
behaviourDatabase = nullptr;
}
}
void Npc::reload()
{
loaded = false;
reset();
load();
if (baseSpeed > 0) {
addEventWalk();
}
}
bool Npc::canSee(const Position& pos) const
{
if (pos.z != getPosition().z) {
return false;
}
return Creature::canSee(getPosition(), pos, 3, 3);
}
std::string Npc::getDescription(int32_t) const
{
std::string descr;
descr.reserve(name.length() + 1);
descr.assign(name);
descr.push_back('.');
return descr;
}
void Npc::onCreatureAppear(Creature* creature, bool isLogin)
{
Creature::onCreatureAppear(creature, isLogin);
if (creature == this) {
if (baseSpeed > 0) {
addEventWalk();
}
} else if (Player* player = creature->getPlayer()) {
spectators.insert(player);
updateIdleStatus();
}
}
void Npc::onRemoveCreature(Creature* creature, bool isLogout)
{
Creature::onRemoveCreature(creature, isLogout);
if (!behaviourDatabase) {
return;
}
Player* player = creature->getPlayer();
if (player) {
if (player->getID() == focusCreature) {
behaviourDatabase->react(SITUATION_VANISH, player, "");
}
spectators.erase(player);
updateIdleStatus();
}
}
void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos,
const Tile* oldTile, const Position& oldPos, bool teleport)
{
Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport);
if (!behaviourDatabase) {
return;
}
Player* player = creature->getPlayer();
if (player && player->getID() == focusCreature) {
if (!Position::areInRange<3, 3, 0>(creature->getPosition(), getPosition())) {
behaviourDatabase->react(SITUATION_VANISH, player, "");
}
}
if (creature != this) {
if (player) {
if (player->canSee(position)) {
spectators.insert(player);
} else {
spectators.erase(player);
}
updateIdleStatus();
}
}
}
void Npc::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text)
{
if (creature->getID() == id || type != TALKTYPE_SAY || !behaviourDatabase) {
return;
}
Player* player = creature->getPlayer();
if (player) {
if (!Position::areInRange<3, 3>(creature->getPosition(), getPosition())) {
return;
}
lastTalkCreature = creature->getID();
if (focusCreature == 0) {
behaviourDatabase->react(SITUATION_ADDRESS, player, text);
} else if (focusCreature != player->getID()) {
behaviourDatabase->react(SITUATION_BUSY, player, text);
} else if (focusCreature == player->getID()) {
behaviourDatabase->react(SITUATION_NONE, player, text);
}
}
}
void Npc::onThink(uint32_t interval)
{
Creature::onThink(interval);
if (!isIdle && focusCreature == 0 && baseSpeed > 0 && getTimeSinceLastMove() >= 100 + getStepDuration()) {
addEventWalk();
}
if (!behaviourDatabase) {
return;
}
if (focusCreature) {
Player* player = g_game.getPlayerByID(focusCreature);
if (player) {
turnToCreature(player);
if (conversationEndTime != 0 && OTSYS_TIME() > conversationEndTime) {
if (player) {
behaviourDatabase->react(SITUATION_VANISH, player, "");
}
}
}
}
}
void Npc::doSay(const std::string& text)
{
if (lastTalkCreature == focusCreature) {
conversationEndTime = OTSYS_TIME() + 60000;
}
g_game.internalCreatureSay(this, TALKTYPE_SAY, text, false);
}
bool Npc::getNextStep(Direction& dir, uint32_t& flags)
{
if (Creature::getNextStep(dir, flags)) {
return true;
}
if (baseSpeed <= 0) {
return false;
}
if (focusCreature != 0) {
return false;
}
if (OTSYS_TIME() < staticMovementTime) {
return false;
}
if (getTimeSinceLastMove() < 100 + getStepDuration() + getStepSpeed()) {
return false;
}
return getRandomStep(dir);
}
void Npc::setIdle(bool idle)
{
if (isRemoved() || getHealth() <= 0) {
return;
}
isIdle = idle;
if (isIdle) {
onIdleStatus();
}
}
void Npc::updateIdleStatus()
{
bool status = spectators.empty();
if (status != isIdle) {
setIdle(status);
}
}
bool Npc::canWalkTo(const Position& fromPos, Direction dir) const
{
if (masterRadius == 0) {
return false;
}
Position toPos = getNextPosition(dir, fromPos);
if (!Spawns::isInZone(masterPos, masterRadius, toPos)) {
return false;
}
Tile* tile = g_game.map.getTile(toPos);
if (!tile || tile->queryAdd(0, *this, 1, 0) != RETURNVALUE_NOERROR) {
return false;
}
if (tile->hasFlag(TILESTATE_BLOCKPATH)) {
return false;
}
if (tile->hasHeight(1)) {
return false;
}
return true;
}
bool Npc::getRandomStep(Direction& dir) const
{
std::vector<Direction> dirList;
const Position& creaturePos = getPosition();
if (canWalkTo(creaturePos, DIRECTION_NORTH)) {
dirList.push_back(DIRECTION_NORTH);
}
if (canWalkTo(creaturePos, DIRECTION_SOUTH)) {
dirList.push_back(DIRECTION_SOUTH);
}
if (canWalkTo(creaturePos, DIRECTION_EAST)) {
dirList.push_back(DIRECTION_EAST);
}
if (canWalkTo(creaturePos, DIRECTION_WEST)) {
dirList.push_back(DIRECTION_WEST);
}
if (dirList.empty()) {
return false;
}
dir = dirList[uniform_random(0, dirList.size() - 1)];
return true;
}
void Npc::doMoveTo(const Position& target)
{
std::forward_list<Direction> listDir;
if (getPathTo(target, listDir, 1, 1, true, true)) {
startAutoWalk(listDir);
}
}
void Npc::turnToCreature(Creature* creature)
{
const Position& creaturePos = creature->getPosition();
const Position& myPos = getPosition();
const auto dx = Position::getOffsetX(myPos, creaturePos);
const auto dy = Position::getOffsetY(myPos, creaturePos);
float tan;
if (dx != 0) {
tan = static_cast<float>(dy) / dx;
} else {
tan = 10;
}
Direction dir;
if (std::abs(tan) < 1) {
if (dx > 0) {
dir = DIRECTION_WEST;
} else {
dir = DIRECTION_EAST;
}
} else {
if (dy > 0) {
dir = DIRECTION_NORTH;
} else {
dir = DIRECTION_SOUTH;
}
}
g_game.internalCreatureTurn(this, dir);
}
void Npc::setCreatureFocus(Creature* creature)
{
if (creature) {
focusCreature = creature->getID();
turnToCreature(creature);
} else {
focusCreature = 0;
}
}

158
src/npc.h Normal file
View File

@@ -0,0 +1,158 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_NPC_H_B090D0CB549D4435AFA03647195D156F
#define FS_NPC_H_B090D0CB549D4435AFA03647195D156F
#include "creature.h"
#include <set>
class Npc;
class Player;
class BehaviourDatabase;
class Npcs
{
public:
static void loadNpcs();
static void reload();
};
class Npc final : public Creature
{
public:
~Npc();
// non-copyable
Npc(const Npc&) = delete;
Npc& operator=(const Npc&) = delete;
Npc* getNpc() final {
return this;
}
const Npc* getNpc() const final {
return this;
}
bool isPushable() const final {
return baseSpeed > 0;
}
void setID() final {
if (id == 0) {
id = npcAutoID++;
}
}
void removeList() final;
void addList() final;
static Npc* createNpc(const std::string& name);
bool canSee(const Position& pos) const final;
bool load();
void reload();
const std::string& getName() const final {
return name;
}
const std::string& getNameDescription() const final {
return name;
}
void doSay(const std::string& text);
void doMoveTo(const Position& pos);
int32_t getMasterRadius() const {
return masterRadius;
}
const Position& getMasterPos() const {
return masterPos;
}
void setMasterPos(Position pos, int32_t radius = 1) {
masterPos = pos;
if (masterRadius == 0) {
masterRadius = radius;
}
}
void turnToCreature(Creature* creature);
void setCreatureFocus(Creature* creature);
static uint32_t npcAutoID;
protected:
explicit Npc(const std::string& name);
void onCreatureAppear(Creature* creature, bool isLogin) final;
void onRemoveCreature(Creature* creature, bool isLogout) final;
void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos,
const Tile* oldTile, const Position& oldPos, bool teleport) final;
void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) final;
void onThink(uint32_t interval) final;
std::string getDescription(int32_t lookDistance) const final;
bool isImmune(CombatType_t) const final {
return true;
}
bool isImmune(ConditionType_t) const final {
return true;
}
bool isAttackable() const final {
return false;
}
bool getNextStep(Direction& dir, uint32_t& flags) final;
void setIdle(bool idle);
void updateIdleStatus();
bool canWalkTo(const Position& fromPos, Direction dir) const;
bool getRandomStep(Direction& dir) const;
void reset();
std::set<Player*> spectators;
std::string name;
std::string filename;
Position masterPos;
uint32_t lastTalkCreature;
uint32_t focusCreature;
uint32_t masterRadius;
int64_t conversationStartTime;
int64_t conversationEndTime;
int64_t staticMovementTime;
bool loaded;
bool isIdle;
BehaviourDatabase* behaviourDatabase;
friend class Npcs;
friend class BehaviourDatabase;
};
#endif

20
src/otpch.cpp Normal file
View File

@@ -0,0 +1,20 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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"

44
src/otpch.h Normal file
View File

@@ -0,0 +1,44 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#define FS_OTPCH_H_F00C737DA6CA4C8D90F57430C614367F
// Definitions should be global.
#include "definitions.h"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <forward_list>
#include <functional>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#include <boost/asio.hpp>
#include <pugixml.hpp>

281
src/otserv.cpp Normal file
View File

@@ -0,0 +1,281 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "server.h"
#include "game.h"
#ifndef _WIN32
#include <csignal> // for sigemptyset()
#endif
#include "configmanager.h"
#include "scriptmanager.h"
#include "rsa.h"
#include "protocollogin.h"
#include "protocolstatus.h"
#include "databasemanager.h"
#include "scheduler.h"
#include "databasetasks.h"
DatabaseTasks g_databaseTasks;
Dispatcher g_dispatcher;
Scheduler g_scheduler;
Game g_game;
ConfigManager g_config;
Monsters g_monsters;
Vocations g_vocations;
RSA g_RSA;
std::mutex g_loaderLock;
std::condition_variable g_loaderSignal;
std::unique_lock<std::mutex> g_loaderUniqueLock(g_loaderLock);
void startupErrorMessage(const std::string& errorStr)
{
std::cout << "> ERROR: " << errorStr << std::endl;
g_loaderSignal.notify_all();
}
void mainLoader(int argc, char* argv[], ServiceManager* servicer);
void badAllocationHandler()
{
// Use functions that only use stack allocation
puts("Allocation failed, server out of memory.\nDecrease the size of your map or compile in 64 bits mode.\n");
getchar();
exit(-1);
}
int main(int argc, char* argv[])
{
// Setup bad allocation handler
std::set_new_handler(badAllocationHandler);
#ifndef _WIN32
// ignore sigpipe...
struct sigaction sigh;
sigh.sa_handler = SIG_IGN;
sigh.sa_flags = 0;
sigemptyset(&sigh.sa_mask);
sigaction(SIGPIPE, &sigh, nullptr);
#endif
ServiceManager serviceManager;
g_dispatcher.start();
g_scheduler.start();
g_dispatcher.addTask(createTask(std::bind(mainLoader, argc, argv, &serviceManager)));
g_loaderSignal.wait(g_loaderUniqueLock);
if (serviceManager.is_running()) {
std::cout << ">> " << g_config.getString(ConfigManager::SERVER_NAME) << " Server Online!" << std::endl << std::endl;
#ifdef _WIN32
SetConsoleCtrlHandler([](DWORD) -> BOOL {
g_dispatcher.addTask(createTask([]() {
g_dispatcher.addTask(createTask(
std::bind(&Game::shutdown, &g_game)
));
g_scheduler.stop();
g_databaseTasks.stop();
g_dispatcher.stop();
}));
ExitThread(0);
}, 1);
#endif
serviceManager.run();
} else {
std::cout << ">> No services running. The server is NOT online." << std::endl;
g_scheduler.shutdown();
g_databaseTasks.shutdown();
g_dispatcher.shutdown();
}
g_scheduler.join();
g_databaseTasks.join();
g_dispatcher.join();
return 0;
}
void mainLoader(int, char*[], ServiceManager* services)
{
//dispatcher thread
g_game.setGameState(GAME_STATE_STARTUP);
srand(static_cast<unsigned int>(OTSYS_TIME()));
#ifdef _WIN32
SetConsoleTitle(STATUS_SERVER_NAME);
#endif
std::cout << STATUS_SERVER_NAME << " - Version " << STATUS_SERVER_VERSION << std::endl;
std::cout << "Compiled with " << BOOST_COMPILER << std::endl;
std::cout << "Compiled on " << __DATE__ << ' ' << __TIME__ << " for platform ";
#if defined(__amd64__) || defined(_M_X64)
std::cout << "x64" << std::endl;
#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_)
std::cout << "x86" << std::endl;
#elif defined(__arm__)
std::cout << "ARM" << std::endl;
#else
std::cout << "unknown" << std::endl;
#endif
std::cout << std::endl;
std::cout << "A server developed by " << STATUS_SERVER_DEVELOPERS << std::endl;
std::cout << "Visit our forum for updates, support, and resources: http://otland.net/." << std::endl;
std::cout << std::endl;
// read global config
std::cout << ">> Loading config" << std::endl;
if (!g_config.load()) {
startupErrorMessage("Unable to load config.lua!");
return;
}
#ifdef _WIN32
const std::string& defaultPriority = g_config.getString(ConfigManager::DEFAULT_PRIORITY);
if (strcasecmp(defaultPriority.c_str(), "high") == 0) {
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
} else if (strcasecmp(defaultPriority.c_str(), "above-normal") == 0) {
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
}
#endif
//set RSA key
const char* p("14299623962416399520070177382898895550795403345466153217470516082934737582776038882967213386204600674145392845853859217990626450972452084065728686565928113");
const char* q("7630979195970404721891201847792002125535401292779123937207447574596692788513647179235335529307251350570728407373705564708871762033017096809910315212884101");
g_RSA.setKey(p, q);
std::cout << ">> Establishing database connection..." << std::flush;
Database* db = Database::getInstance();
if (!db->connect()) {
startupErrorMessage("Failed to connect to database.");
return;
}
std::cout << " MySQL " << Database::getClientVersion() << std::endl;
// run database manager
std::cout << ">> Running database manager" << std::endl;
if (!DatabaseManager::isDatabaseSetup()) {
startupErrorMessage("The database you have specified in config.lua is empty, please import the schema.sql to your database.");
return;
}
g_databaseTasks.start();
if (g_config.getBoolean(ConfigManager::OPTIMIZE_DATABASE) && !DatabaseManager::optimizeTables()) {
std::cout << "> No tables were optimized." << std::endl;
}
//load vocations
std::cout << ">> Loading vocations" << std::endl;
if (!g_vocations.loadFromXml()) {
startupErrorMessage("Unable to load vocations!");
return;
}
// load item data
std::cout << ">> Loading items" << std::endl;
if (!Item::items.loadItems()) {
startupErrorMessage("Unable to load items (SRV)!");
return;
}
std::cout << ">> Loading script systems" << std::endl;
if (!ScriptingManager::getInstance()->loadScriptSystems()) {
startupErrorMessage("Failed to load script systems");
return;
}
std::cout << ">> Loading monsters" << std::endl;
if (!g_monsters.loadFromXml()) {
startupErrorMessage("Unable to load monsters!");
return;
}
std::cout << ">> Checking world type... " << std::flush;
std::string worldType = asLowerCaseString(g_config.getString(ConfigManager::WORLD_TYPE));
if (worldType == "pvp") {
g_game.setWorldType(WORLD_TYPE_PVP);
} else if (worldType == "no-pvp") {
g_game.setWorldType(WORLD_TYPE_NO_PVP);
} else if (worldType == "pvp-enforced") {
g_game.setWorldType(WORLD_TYPE_PVP_ENFORCED);
} else {
std::cout << std::endl;
std::ostringstream ss;
ss << "> ERROR: Unknown world type: " << g_config.getString(ConfigManager::WORLD_TYPE) << ", valid world types are: pvp, no-pvp and pvp-enforced.";
startupErrorMessage(ss.str());
return;
}
std::cout << asUpperCaseString(worldType) << std::endl;
std::cout << ">> Loading map" << std::endl;
if (!g_game.loadMainMap(g_config.getString(ConfigManager::MAP_NAME))) {
startupErrorMessage("Failed to load map");
return;
}
std::cout << ">> Initializing gamestate" << std::endl;
g_game.setGameState(GAME_STATE_INIT);
// Game client protocols
services->add<ProtocolGame>(g_config.getNumber(ConfigManager::GAME_PORT));
services->add<ProtocolLogin>(g_config.getNumber(ConfigManager::LOGIN_PORT));
// OT protocols
services->add<ProtocolStatus>(g_config.getNumber(ConfigManager::STATUS_PORT));
RentPeriod_t rentPeriod;
std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD));
if (strRentPeriod == "yearly") {
rentPeriod = RENTPERIOD_YEARLY;
} else if (strRentPeriod == "weekly") {
rentPeriod = RENTPERIOD_WEEKLY;
} else if (strRentPeriod == "monthly") {
rentPeriod = RENTPERIOD_MONTHLY;
} else if (strRentPeriod == "daily") {
rentPeriod = RENTPERIOD_DAILY;
} else {
rentPeriod = RENTPERIOD_NEVER;
}
g_game.map.houses.payHouses(rentPeriod);
std::cout << ">> Loaded all modules, server starting up..." << std::endl;
#ifndef _WIN32
if (getuid() == 0 || geteuid() == 0) {
std::cout << "> Warning: " << STATUS_SERVER_NAME << " has been executed as root user, please consider running it as a normal user." << std::endl;
}
#endif
g_game.start(services);
g_game.setGameState(GAME_STATE_NORMAL);
g_loaderSignal.notify_all();
}

83
src/outputmessage.cpp Normal file
View File

@@ -0,0 +1,83 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "outputmessage.h"
#include "protocol.h"
#include "lockfree.h"
#include "scheduler.h"
extern Scheduler g_scheduler;
const uint16_t OUTPUTMESSAGE_FREE_LIST_CAPACITY = 2048;
const std::chrono::milliseconds OUTPUTMESSAGE_AUTOSEND_DELAY {10};
class OutputMessageAllocator
{
public:
typedef OutputMessage value_type;
template<typename U>
struct rebind {typedef LockfreePoolingAllocator<U, OUTPUTMESSAGE_FREE_LIST_CAPACITY> other;};
};
void OutputMessagePool::scheduleSendAll()
{
auto functor = std::bind(&OutputMessagePool::sendAll, this);
g_scheduler.addEvent(createSchedulerTask(OUTPUTMESSAGE_AUTOSEND_DELAY.count(), functor));
}
void OutputMessagePool::sendAll()
{
//dispatcher thread
for (auto& protocol : bufferedProtocols) {
auto& msg = protocol->getCurrentBuffer();
if (msg) {
protocol->send(std::move(msg));
}
}
if (!bufferedProtocols.empty()) {
scheduleSendAll();
}
}
void OutputMessagePool::addProtocolToAutosend(Protocol_ptr protocol)
{
//dispatcher thread
if (bufferedProtocols.empty()) {
scheduleSendAll();
}
bufferedProtocols.emplace_back(protocol);
}
void OutputMessagePool::removeProtocolFromAutosend(const Protocol_ptr& protocol)
{
//dispatcher thread
auto it = std::find(bufferedProtocols.begin(), bufferedProtocols.end(), protocol);
if (it != bufferedProtocols.end()) {
std::swap(*it, bufferedProtocols.back());
bufferedProtocols.pop_back();
}
}
OutputMessage_ptr OutputMessagePool::getOutputMessage()
{
return std::allocate_shared<OutputMessage>(OutputMessageAllocator());
}

104
src/outputmessage.h Normal file
View File

@@ -0,0 +1,104 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_OUTPUTMESSAGE_H_C06AAED85C7A43939F22D229297C0CC1
#define FS_OUTPUTMESSAGE_H_C06AAED85C7A43939F22D229297C0CC1
#include "networkmessage.h"
#include "connection.h"
#include "tools.h"
class Protocol;
class OutputMessage : public NetworkMessage
{
public:
OutputMessage() = default;
// non-copyable
OutputMessage(const OutputMessage&) = delete;
OutputMessage& operator=(const OutputMessage&) = delete;
uint8_t* getOutputBuffer() {
return buffer + outputBufferStart;
}
void writeMessageLength() {
add_header(info.length);
}
void addCryptoHeader() {
writeMessageLength();
}
inline void append(const NetworkMessage& msg) {
auto msgLen = msg.getLength();
memcpy(buffer + info.position, msg.getBuffer() + 4, msgLen);
info.length += msgLen;
info.position += msgLen;
}
inline void append(const OutputMessage_ptr& msg) {
auto msgLen = msg->getLength();
memcpy(buffer + info.position, msg->getBuffer() + 4, msgLen);
info.length += msgLen;
info.position += msgLen;
}
protected:
template <typename T>
inline void add_header(T add) {
assert(outputBufferStart >= sizeof(T));
outputBufferStart -= sizeof(T);
memcpy(buffer + outputBufferStart, &add, sizeof(T));
//added header size to the message size
info.length += sizeof(T);
}
MsgSize_t outputBufferStart = INITIAL_BUFFER_POSITION;
};
class OutputMessagePool
{
public:
// non-copyable
OutputMessagePool(const OutputMessagePool&) = delete;
OutputMessagePool& operator=(const OutputMessagePool&) = delete;
static OutputMessagePool& getInstance() {
static OutputMessagePool instance;
return instance;
}
void sendAll();
void scheduleSendAll();
static OutputMessage_ptr getOutputMessage();
void addProtocolToAutosend(Protocol_ptr protocol);
void removeProtocolFromAutosend(const Protocol_ptr& protocol);
private:
OutputMessagePool() = default;
//NOTE: A vector is used here because this container is mostly read
//and relatively rarely modified (only when a client connects/disconnects)
std::vector<Protocol_ptr> bufferedProtocols;
};
#endif

458
src/party.cpp Normal file
View File

@@ -0,0 +1,458 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "party.h"
#include "game.h"
#include "configmanager.h"
extern Game g_game;
extern ConfigManager g_config;
Party::Party(Player* leader) : leader(leader)
{
leader->setParty(this);
}
void Party::disband()
{
Player* currentLeader = leader;
leader = nullptr;
currentLeader->setParty(nullptr);
currentLeader->sendClosePrivate(CHANNEL_PARTY);
g_game.updatePlayerShield(currentLeader);
currentLeader->sendCreatureSkull(currentLeader);
currentLeader->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded.");
for (Player* invitee : inviteList) {
invitee->removePartyInvitation(this);
currentLeader->sendCreatureShield(invitee);
}
inviteList.clear();
for (Player* member : memberList) {
member->setParty(nullptr);
member->sendClosePrivate(CHANNEL_PARTY);
member->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded.");
}
for (Player* member : memberList) {
g_game.updatePlayerShield(member);
for (Player* otherMember : memberList) {
otherMember->sendCreatureSkull(member);
}
member->sendCreatureSkull(currentLeader);
currentLeader->sendCreatureSkull(member);
}
memberList.clear();
delete this;
}
bool Party::leaveParty(Player* player)
{
if (!player) {
return false;
}
if (player->getParty() != this && leader != player) {
return false;
}
bool missingLeader = false;
if (leader == player) {
if (!memberList.empty()) {
if (memberList.size() == 1 && inviteList.empty()) {
missingLeader = true;
} else {
passPartyLeadership(memberList.front());
}
} else {
missingLeader = true;
}
}
//since we already passed the leadership, we remove the player from the list
auto it = std::find(memberList.begin(), memberList.end(), player);
if (it != memberList.end()) {
memberList.erase(it);
}
player->setParty(nullptr);
player->sendClosePrivate(CHANNEL_PARTY);
g_game.updatePlayerShield(player);
for (Player* member : memberList) {
member->sendCreatureSkull(player);
player->sendPlayerPartyIcons(member);
}
leader->sendCreatureSkull(player);
player->sendCreatureSkull(player);
player->sendPlayerPartyIcons(leader);
player->sendTextMessage(MESSAGE_INFO_DESCR, "You have left the party.");
updateSharedExperience();
updateVocationsList();
clearPlayerPoints(player);
std::ostringstream ss;
ss << player->getName() << " has left the party.";
broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str());
if (missingLeader || empty()) {
disband();
}
return true;
}
bool Party::passPartyLeadership(Player* player)
{
if (!player || leader == player || player->getParty() != this) {
return false;
}
//Remove it before to broadcast the message correctly
auto it = std::find(memberList.begin(), memberList.end(), player);
if (it != memberList.end()) {
memberList.erase(it);
}
std::ostringstream ss;
ss << player->getName() << " is now the leader of the party.";
broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str(), true);
Player* oldLeader = leader;
leader = player;
memberList.insert(memberList.begin(), oldLeader);
updateSharedExperience();
for (Player* member : memberList) {
member->sendCreatureShield(oldLeader);
member->sendCreatureShield(leader);
}
for (Player* invitee : inviteList) {
invitee->sendCreatureShield(oldLeader);
invitee->sendCreatureShield(leader);
}
leader->sendCreatureShield(oldLeader);
leader->sendCreatureShield(leader);
player->sendTextMessage(MESSAGE_INFO_DESCR, "You are now the leader of the party.");
return true;
}
bool Party::joinParty(Player& player)
{
auto it = std::find(inviteList.begin(), inviteList.end(), &player);
if (it == inviteList.end()) {
return false;
}
inviteList.erase(it);
std::ostringstream ss;
ss << player.getName() << " has joined the party.";
broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str());
player.setParty(this);
g_game.updatePlayerShield(&player);
for (Player* member : memberList) {
member->sendCreatureSkull(&player);
player.sendPlayerPartyIcons(member);
}
player.sendCreatureSkull(&player);
leader->sendCreatureSkull(&player);
player.sendPlayerPartyIcons(leader);
memberList.push_back(&player);
player.removePartyInvitation(this);
updateSharedExperience();
updateVocationsList();
const std::string& leaderName = leader->getName();
ss.str(std::string());
ss << "You have joined " << leaderName << "'" << (leaderName.back() == 's' ? "" : "s") <<
" party. Open the party channel to communicate with your companions.";
player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
return true;
}
bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/)
{
auto it = std::find(inviteList.begin(), inviteList.end(), &player);
if (it == inviteList.end()) {
return false;
}
inviteList.erase(it);
leader->sendCreatureShield(&player);
player.sendCreatureShield(leader);
if (removeFromPlayer) {
player.removePartyInvitation(this);
}
if (empty()) {
disband();
}
return true;
}
void Party::revokeInvitation(Player& player)
{
std::ostringstream ss;
ss << leader->getName() << " has revoked " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " invitation.";
player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
ss.str(std::string());
ss << "Invitation for " << player.getName() << " has been revoked.";
leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
removeInvite(player);
}
bool Party::invitePlayer(Player& player)
{
if (isPlayerInvited(&player)) {
return false;
}
std::ostringstream ss;
ss << player.getName() << " has been invited.";
if (memberList.empty() && inviteList.empty()) {
ss << " Open the party channel to communicate with your members.";
g_game.updatePlayerShield(leader);
leader->sendCreatureSkull(leader);
}
leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
inviteList.push_back(&player);
leader->sendCreatureShield(&player);
player.sendCreatureShield(leader);
player.addPartyInvitation(this);
ss.str(std::string());
ss << leader->getName() << " has invited you to " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " party.";
player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
return true;
}
bool Party::isPlayerInvited(const Player* player) const
{
return std::find(inviteList.begin(), inviteList.end(), player) != inviteList.end();
}
void Party::updateAllPartyIcons()
{
for (Player* member : memberList) {
for (Player* otherMember : memberList) {
member->sendCreatureShield(otherMember);
}
member->sendCreatureShield(leader);
leader->sendCreatureShield(member);
}
leader->sendCreatureShield(leader);
}
void Party::broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations /*= false*/)
{
for (Player* member : memberList) {
member->sendTextMessage(msgClass, msg);
}
leader->sendTextMessage(msgClass, msg);
if (sendToInvitations) {
for (Player* invitee : inviteList) {
invitee->sendTextMessage(msgClass, msg);
}
}
}
void Party::broadcastPartyLoot(const std::string& loot)
{
leader->sendTextMessage(MESSAGE_INFO_DESCR, loot);
for (Player* member : memberList) {
member->sendTextMessage(MESSAGE_INFO_DESCR, loot);
}
}
void Party::updateSharedExperience()
{
if (sharedExpActive) {
bool result = canEnableSharedExperience();
if (result != sharedExpEnabled) {
sharedExpEnabled = result;
updateAllPartyIcons();
}
}
}
void Party::updateVocationsList()
{
std::set<uint32_t> vocationIds;
uint32_t vocationId = leader->getVocation()->getFromVocation();
if (vocationId != VOCATION_NONE) {
vocationIds.insert(vocationId);
}
for (const Player* member : memberList) {
vocationId = member->getVocation()->getFromVocation();
if (vocationId != VOCATION_NONE) {
vocationIds.insert(vocationId);
}
}
size_t size = vocationIds.size();
if (size > 1) {
extraExpRate = static_cast<float>(size * (10 + (size - 1) * 5)) / 100.f;
} else {
extraExpRate = 0.20f;
}
}
bool Party::setSharedExperience(Player* player, bool sharedExpActive)
{
if (!player || leader != player) {
return false;
}
if (this->sharedExpActive == sharedExpActive) {
return true;
}
this->sharedExpActive = sharedExpActive;
if (sharedExpActive) {
this->sharedExpEnabled = canEnableSharedExperience();
if (this->sharedExpEnabled) {
leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience is now active.");
} else {
leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been activated, but some members of your party are inactive.");
}
} else {
leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been deactivated.");
}
updateAllPartyIcons();
return true;
}
void Party::shareExperience(uint64_t experience)
{
uint64_t shareExperience = static_cast<uint64_t>(std::ceil((static_cast<double>(experience) * (extraExpRate + 1)) / (memberList.size() + 1)));
for (Player* member : memberList) {
member->onGainSharedExperience(shareExperience);
}
leader->onGainSharedExperience(shareExperience);
}
bool Party::canUseSharedExperience(const Player* player) const
{
if (memberList.empty()) {
return false;
}
uint32_t highestLevel = leader->getLevel();
for (Player* member : memberList) {
if (member->getLevel() > highestLevel) {
highestLevel = member->getLevel();
}
}
uint32_t minLevel = static_cast<int32_t>(std::ceil((static_cast<float>(highestLevel) * 2) / 3));
if (player->getLevel() < minLevel) {
return false;
}
if (!Position::areInRange<30, 30, 1>(leader->getPosition(), player->getPosition())) {
return false;
}
if (!player->hasFlag(PlayerFlag_NotGainInFight)) {
//check if the player has healed/attacked anything recently
auto it = ticksMap.find(player->getID());
if (it == ticksMap.end()) {
return false;
}
uint64_t timeDiff = OTSYS_TIME() - it->second;
if (timeDiff > static_cast<uint64_t>(g_config.getNumber(ConfigManager::PZ_LOCKED))) {
return false;
}
}
return true;
}
bool Party::canEnableSharedExperience()
{
if (!canUseSharedExperience(leader)) {
return false;
}
for (Player* member : memberList) {
if (!canUseSharedExperience(member)) {
return false;
}
}
return true;
}
void Party::updatePlayerTicks(Player* player, uint32_t points)
{
if (points != 0 && !player->hasFlag(PlayerFlag_NotGainInFight)) {
ticksMap[player->getID()] = OTSYS_TIME();
updateSharedExperience();
}
}
void Party::clearPlayerPoints(Player* player)
{
auto it = ticksMap.find(player->getID());
if (it != ticksMap.end()) {
ticksMap.erase(it);
updateSharedExperience();
}
}

101
src/party.h Normal file
View File

@@ -0,0 +1,101 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044
#define FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044
#include "player.h"
#include "monsters.h"
class Player;
class Party;
typedef std::vector<Player*> PlayerVector;
class Party
{
public:
explicit Party(Player* leader);
Player* getLeader() const {
return leader;
}
PlayerVector& getMembers() {
return memberList;
}
const PlayerVector& getInvitees() const {
return inviteList;
}
size_t getMemberCount() const {
return memberList.size();
}
size_t getInvitationCount() const {
return inviteList.size();
}
void disband();
bool invitePlayer(Player& player);
bool joinParty(Player& player);
void revokeInvitation(Player& player);
bool passPartyLeadership(Player* player);
bool leaveParty(Player* player);
bool removeInvite(Player& player, bool removeFromPlayer = true);
bool isPlayerInvited(const Player* player) const;
void updateAllPartyIcons();
void broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations = false);
void broadcastPartyLoot(const std::string& loot);
bool empty() const {
return memberList.empty() && inviteList.empty();
}
void shareExperience(uint64_t experience);
bool setSharedExperience(Player* player, bool sharedExpActive);
bool isSharedExperienceActive() const {
return sharedExpActive;
}
bool isSharedExperienceEnabled() const {
return sharedExpEnabled;
}
bool canUseSharedExperience(const Player* player) const;
void updateSharedExperience();
void updateVocationsList();
void updatePlayerTicks(Player* player, uint32_t points);
void clearPlayerPoints(Player* player);
protected:
bool canEnableSharedExperience();
std::map<uint32_t, int64_t> ticksMap;
PlayerVector memberList;
PlayerVector inviteList;
Player* leader;
float extraExpRate = 0.20f;
bool sharedExpActive = false;
bool sharedExpEnabled = false;
};
#endif

3665
src/player.cpp Normal file

File diff suppressed because it is too large Load Diff

1099
src/player.h Normal file

File diff suppressed because it is too large Load Diff

73
src/position.cpp Normal file
View File

@@ -0,0 +1,73 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "position.h"
std::ostream& operator<<(std::ostream& os, const Position& pos)
{
os << "( " << std::setw(5) << std::setfill('0') << pos.x;
os << " / " << std::setw(5) << std::setfill('0') << pos.y;
os << " / " << std::setw(3) << std::setfill('0') << pos.getZ();
os << " )";
return os;
}
std::ostream& operator<<(std::ostream& os, const Direction& dir)
{
switch (dir) {
case DIRECTION_NORTH:
os << "North";
break;
case DIRECTION_EAST:
os << "East";
break;
case DIRECTION_WEST:
os << "West";
break;
case DIRECTION_SOUTH:
os << "South";
break;
case DIRECTION_SOUTHWEST:
os << "South-West";
break;
case DIRECTION_SOUTHEAST:
os << "South-East";
break;
case DIRECTION_NORTHWEST:
os << "North-West";
break;
case DIRECTION_NORTHEAST:
os << "North-East";
break;
default:
break;
}
return os;
}

134
src/position.h Normal file
View File

@@ -0,0 +1,134 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_POSITION_H_5B684192F7034FB8857C8280D2CC6C75
#define FS_POSITION_H_5B684192F7034FB8857C8280D2CC6C75
enum Direction : uint8_t {
DIRECTION_NORTH = 0,
DIRECTION_EAST = 1,
DIRECTION_SOUTH = 2,
DIRECTION_WEST = 3,
DIRECTION_DIAGONAL_MASK = 4,
DIRECTION_SOUTHWEST = DIRECTION_DIAGONAL_MASK | 0,
DIRECTION_SOUTHEAST = DIRECTION_DIAGONAL_MASK | 1,
DIRECTION_NORTHWEST = DIRECTION_DIAGONAL_MASK | 2,
DIRECTION_NORTHEAST = DIRECTION_DIAGONAL_MASK | 3,
DIRECTION_LAST = DIRECTION_NORTHEAST,
DIRECTION_NONE = 8,
};
struct Position
{
constexpr Position() = default;
constexpr Position(uint16_t x, uint16_t y, uint8_t z) : x(x), y(y), z(z) {}
template<int_fast32_t deltax, int_fast32_t deltay>
inline static bool areInRange(const Position& p1, const Position& p2) {
return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay;
}
template<int_fast32_t deltax, int_fast32_t deltay, int_fast16_t deltaz>
inline static bool areInRange(const Position& p1, const Position& p2) {
return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay && Position::getDistanceZ(p1, p2) <= deltaz;
}
inline static int_fast32_t getOffsetX(const Position& p1, const Position& p2) {
return p1.getX() - p2.getX();
}
inline static int_fast32_t getOffsetY(const Position& p1, const Position& p2) {
return p1.getY() - p2.getY();
}
inline static int_fast16_t getOffsetZ(const Position& p1, const Position& p2) {
return p1.getZ() - p2.getZ();
}
inline static int32_t getDistanceX(const Position& p1, const Position& p2) {
return std::abs(Position::getOffsetX(p1, p2));
}
inline static int32_t getDistanceY(const Position& p1, const Position& p2) {
return std::abs(Position::getOffsetY(p1, p2));
}
inline static int16_t getDistanceZ(const Position& p1, const Position& p2) {
return std::abs(Position::getOffsetZ(p1, p2));
}
uint16_t x = 0;
uint16_t y = 0;
uint8_t z = 0;
bool operator<(const Position& p) const {
if (z < p.z) {
return true;
}
if (z > p.z) {
return false;
}
if (y < p.y) {
return true;
}
if (y > p.y) {
return false;
}
if (x < p.x) {
return true;
}
if (x > p.x) {
return false;
}
return false;
}
bool operator>(const Position& p) const {
return ! (*this < p);
}
bool operator==(const Position& p) const {
return p.x == x && p.y == y && p.z == z;
}
bool operator!=(const Position& p) const {
return p.x != x || p.y != y || p.z != z;
}
Position operator+(const Position& p1) const {
return Position(x + p1.x, y + p1.y, z + p1.z);
}
Position operator-(const Position& p1) const {
return Position(x - p1.x, y - p1.y, z - p1.z);
}
inline int_fast32_t getX() const { return x; }
inline int_fast32_t getY() const { return y; }
inline int_fast16_t getZ() const { return z; }
};
std::ostream& operator<<(std::ostream&, const Position&);
std::ostream& operator<<(std::ostream&, const Direction&);
#endif

154
src/protocol.cpp Normal file
View File

@@ -0,0 +1,154 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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 "protocol.h"
#include "outputmessage.h"
#include "rsa.h"
extern RSA g_RSA;
void Protocol::onSendMessage(const OutputMessage_ptr& msg) const
{
if (!rawMessages) {
msg->writeMessageLength();
if (encryptionEnabled) {
XTEA_encrypt(*msg);
msg->addCryptoHeader();
}
}
}
void Protocol::onRecvMessage(NetworkMessage& msg)
{
if (encryptionEnabled && !XTEA_decrypt(msg)) {
return;
}
parsePacket(msg);
}
OutputMessage_ptr Protocol::getOutputBuffer(int32_t size)
{
//dispatcher thread
if (!outputBuffer) {
outputBuffer = OutputMessagePool::getOutputMessage();
} else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) {
send(outputBuffer);
outputBuffer = OutputMessagePool::getOutputMessage();
}
return outputBuffer;
}
void Protocol::XTEA_encrypt(OutputMessage& msg) const
{
const uint32_t delta = 0x61C88647;
// The message must be a multiple of 8
size_t paddingBytes = msg.getLength() % 8;
if (paddingBytes != 0) {
msg.addPaddingBytes(8 - paddingBytes);
}
uint8_t* buffer = msg.getOutputBuffer();
const size_t messageLength = msg.getLength();
size_t readPos = 0;
const uint32_t k[] = {key[0], key[1], key[2], key[3]};
while (readPos < messageLength) {
uint32_t v0;
memcpy(&v0, buffer + readPos, 4);
uint32_t v1;
memcpy(&v1, buffer + readPos + 4, 4);
uint32_t sum = 0;
for (int32_t i = 32; --i >= 0;) {
v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]);
sum -= delta;
v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[(sum >> 11) & 3]);
}
memcpy(buffer + readPos, &v0, 4);
readPos += 4;
memcpy(buffer + readPos, &v1, 4);
readPos += 4;
}
}
bool Protocol::XTEA_decrypt(NetworkMessage& msg) const
{
if (((msg.getLength() - 2) % 8) != 0) {
return false;
}
const uint32_t delta = 0x61C88647;
uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition();
const size_t messageLength = (msg.getLength() - 6);
size_t readPos = 0;
const uint32_t k[] = {key[0], key[1], key[2], key[3]};
while (readPos < messageLength) {
uint32_t v0;
memcpy(&v0, buffer + readPos, 4);
uint32_t v1;
memcpy(&v1, buffer + readPos + 4, 4);
uint32_t sum = 0xC6EF3720;
for (int32_t i = 32; --i >= 0;) {
v1 -= ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[(sum >> 11) & 3]);
sum += delta;
v0 -= ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]);
}
memcpy(buffer + readPos, &v0, 4);
readPos += 4;
memcpy(buffer + readPos, &v1, 4);
readPos += 4;
}
int innerLength = msg.get<uint16_t>();
if (innerLength > msg.getLength() - 4) {
return false;
}
msg.setLength(innerLength);
return true;
}
bool Protocol::RSA_decrypt(NetworkMessage& msg)
{
if ((msg.getLength() - msg.getBufferPosition()) < 128) {
return false;
}
g_RSA.decrypt(reinterpret_cast<char*>(msg.getBuffer()) + msg.getBufferPosition()); //does not break strict aliasing
return msg.getByte() == 0;
}
uint32_t Protocol::getIP() const
{
if (auto connection = getConnection()) {
return connection->getIP();
}
return 0;
}

97
src/protocol.h Normal file
View File

@@ -0,0 +1,97 @@
/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Alejandro Mujica <alejandrodemujica@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.
*/
#ifndef FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1
#define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1
#include "connection.h"
class Protocol : public std::enable_shared_from_this<Protocol>
{
public:
explicit Protocol(Connection_ptr connection) : connection(connection) {}
virtual ~Protocol() = default;
// non-copyable
Protocol(const Protocol&) = delete;
Protocol& operator=(const Protocol&) = delete;
virtual void parsePacket(NetworkMessage&) {}
virtual void onSendMessage(const OutputMessage_ptr& msg) const;
void onRecvMessage(NetworkMessage& msg);
virtual void onRecvFirstMessage(NetworkMessage& msg) = 0;
virtual void onConnect() {}
bool isConnectionExpired() const {
return connection.expired();
}
Connection_ptr getConnection() const {
return connection.lock();
}
uint32_t getIP() const;
//Use this function for autosend messages only
OutputMessage_ptr getOutputBuffer(int32_t size);
OutputMessage_ptr& getCurrentBuffer() {
return outputBuffer;
}
void send(OutputMessage_ptr msg) const {
if (auto connection = getConnection()) {
connection->send(msg);
}
}
protected:
void disconnect() const {
if (auto connection = getConnection()) {
connection->close();
}
}
void enableXTEAEncryption() {
encryptionEnabled = true;
}
void setXTEAKey(const uint32_t* key) {
memcpy(this->key, key, sizeof(*key) * 4);
}
void XTEA_encrypt(OutputMessage& msg) const;
bool XTEA_decrypt(NetworkMessage& msg) const;
static bool RSA_decrypt(NetworkMessage& msg);
void setRawMessages(bool value) {
rawMessages = value;
}
virtual void release() {}
friend class Connection;
OutputMessage_ptr outputBuffer;
private:
const ConnectionWeak_ptr connection;
uint32_t key[4] = {};
bool encryptionEnabled = false;
bool rawMessages = false;
};
#endif

2026
src/protocolgame.cpp Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More